From 0581a42b70b3159a1ed4a8d9a65640115a6ef9df Mon Sep 17 00:00:00 2001 From: Unity Technologies <@unity> Date: Mon, 24 Jul 2023 00:00:00 +0000 Subject: [PATCH] com.unity.netcode.gameobjects@1.5.2 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [1.5.2] - 2023-07-24 ### Added ### Fixed - Fixed issue where `NetworkClient.OwnedObjects` was not returning any owned objects due to the `NetworkClient.IsConnected` not being properly set. (#2631) - Fixed a crash when calling TrySetParent with a null Transform (#2625) - Fixed issue where a `NetworkTransform` using full precision state updates was losing transform state updates when interpolation was enabled. (#2624) - Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored for late joining clients. (#2623) - Fixed issue where invoking `NetworkManager.Shutdown` multiple times, depending upon the timing, could cause an exception. (#2622) - Fixed issue where removing ownership would not notify the server that it gained ownership. This also resolves the issue where an owner authoritative NetworkTransform would not properly initialize upon removing ownership from a remote client. (#2618) - Fixed ILPP issues when using CoreCLR and for certain dedicated server builds. (#2614) - Fixed an ILPP compile error when creating a generic NetworkBehaviour singleton with a static T instance. (#2603) ### Changed --- CHANGELOG.md | 17 ++ Components/NetworkTransform.cs | 29 +++- Editor/CodeGen/CodeGenHelpers.cs | 6 +- Editor/CodeGen/NetworkBehaviourILPP.cs | 30 ++-- Editor/CodeGen/RuntimeAccessModifiersILPP.cs | 15 +- Runtime/Connection/NetworkClient.cs | 4 - .../Connection/NetworkConnectionManager.cs | 22 +-- Runtime/Core/NetworkBehaviour.cs | 4 - Runtime/Core/NetworkBehaviourUpdater.cs | 2 +- Runtime/Core/NetworkManager.cs | 17 +- Runtime/Core/NetworkObject.cs | 8 +- .../Messages/NetworkVariableDeltaMessage.cs | 2 - Runtime/Messaging/Messages/RpcMessages.cs | 8 + .../SceneManagement/NetworkSceneManager.cs | 4 + Runtime/Spawning/NetworkSpawnManager.cs | 75 +++------ Runtime/Timing/NetworkTimeSystem.cs | 3 - .../Editor/Transports/UnityTransportTests.cs | 4 +- Tests/Runtime/NetworkBehaviourGenericTests.cs | 1 - .../NetworkObjectOnSpawnTests.cs | 37 ++++- .../NetworkObjectOwnershipTests.cs | 99 +++++++++++- .../NetworkTransform/NetworkTransformTests.cs | 147 ++++++++++++------ ValidationExceptions.json | 10 ++ ValidationExceptions.json.meta | 7 + package.json | 8 +- 24 files changed, 390 insertions(+), 169 deletions(-) create mode 100644 ValidationExceptions.json create mode 100644 ValidationExceptions.json.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index 46cc459..d40aaba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,23 @@ 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). +## [1.5.2] - 2023-07-24 + +### Added + +### Fixed + +- Fixed issue where `NetworkClient.OwnedObjects` was not returning any owned objects due to the `NetworkClient.IsConnected` not being properly set. (#2631) +- Fixed a crash when calling TrySetParent with a null Transform (#2625) +- Fixed issue where a `NetworkTransform` using full precision state updates was losing transform state updates when interpolation was enabled. (#2624) +- Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored for late joining clients. (#2623) +- Fixed issue where invoking `NetworkManager.Shutdown` multiple times, depending upon the timing, could cause an exception. (#2622) +- Fixed issue where removing ownership would not notify the server that it gained ownership. This also resolves the issue where an owner authoritative NetworkTransform would not properly initialize upon removing ownership from a remote client. (#2618) +- Fixed ILPP issues when using CoreCLR and for certain dedicated server builds. (#2614) +- Fixed an ILPP compile error when creating a generic NetworkBehaviour singleton with a static T instance. (#2603) + +### Changed + ## [1.5.1] - 2023-06-07 ### Added diff --git a/Components/NetworkTransform.cs b/Components/NetworkTransform.cs index 587b263..e1e9667 100644 --- a/Components/NetworkTransform.cs +++ b/Components/NetworkTransform.cs @@ -1159,8 +1159,11 @@ public Vector3 GetScale(bool getCurrentState = false) // 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_TargetPosition; private Vector3 m_CurrentScale; + private Vector3 m_TargetScale; private Quaternion m_CurrentRotation; + private Vector3 m_TargetRotation; [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -2009,6 +2012,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) } m_CurrentPosition = currentPosition; + m_TargetPosition = currentPosition; // Apply the position if (newState.InLocalSpace) @@ -2026,7 +2030,6 @@ private void ApplyTeleportingState(NetworkTransformState newState) if (UseHalfFloatPrecision) { currentScale = newState.Scale; - m_CurrentScale = currentScale; } else { @@ -2049,6 +2052,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) } m_CurrentScale = currentScale; + m_TargetScale = currentScale; m_ScaleInterpolator.ResetTo(currentScale, sentTime); // Apply the adjusted scale @@ -2082,6 +2086,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) } m_CurrentRotation = currentRotation; + m_TargetRotation = currentRotation.eulerAngles; m_RotationInterpolator.ResetTo(currentRotation, sentTime); if (InLocalSpace) @@ -2158,28 +2163,29 @@ private void UpdateState(NetworkTransformState oldState, NetworkTransformState n } else { - var currentPosition = GetSpaceRelativePosition(); + var newTargetPosition = m_TargetPosition; if (m_LocalAuthoritativeNetworkState.HasPositionX) { - currentPosition.x = m_LocalAuthoritativeNetworkState.PositionX; + newTargetPosition.x = m_LocalAuthoritativeNetworkState.PositionX; } if (m_LocalAuthoritativeNetworkState.HasPositionY) { - currentPosition.y = m_LocalAuthoritativeNetworkState.PositionY; + newTargetPosition.y = m_LocalAuthoritativeNetworkState.PositionY; } if (m_LocalAuthoritativeNetworkState.HasPositionZ) { - currentPosition.z = m_LocalAuthoritativeNetworkState.PositionZ; + newTargetPosition.z = m_LocalAuthoritativeNetworkState.PositionZ; } - UpdatePositionInterpolator(currentPosition, sentTime); + UpdatePositionInterpolator(newTargetPosition, sentTime); + m_TargetPosition = newTargetPosition; } } if (m_LocalAuthoritativeNetworkState.HasScaleChange) { - var currentScale = transform.localScale; + var currentScale = m_TargetScale; if (UseHalfFloatPrecision) { for (int i = 0; i < 3; i++) @@ -2207,6 +2213,7 @@ private void UpdateState(NetworkTransformState oldState, NetworkTransformState n currentScale.z = m_LocalAuthoritativeNetworkState.ScaleZ; } } + m_TargetScale = currentScale; m_ScaleInterpolator.AddMeasurement(currentScale, sentTime); } @@ -2221,7 +2228,9 @@ private void UpdateState(NetworkTransformState oldState, NetworkTransformState n } else { + 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) { currentEulerAngles.x = m_LocalAuthoritativeNetworkState.RotAngleX; @@ -2236,6 +2245,7 @@ private void UpdateState(NetworkTransformState oldState, NetworkTransformState n { currentEulerAngles.z = m_LocalAuthoritativeNetworkState.RotAngleZ; } + m_TargetRotation = currentEulerAngles; currentRotation.eulerAngles = currentEulerAngles; } @@ -2489,8 +2499,11 @@ protected void Initialize() ResetInterpolatedStateToCurrentAuthoritativeState(); m_CurrentPosition = currentPosition; + m_TargetPosition = currentPosition; m_CurrentScale = transform.localScale; + m_TargetScale = transform.localScale; m_CurrentRotation = currentRotation; + m_TargetRotation = currentRotation.eulerAngles; } @@ -2649,7 +2662,7 @@ protected virtual void Update() var serverTime = NetworkManager.ServerTime; var cachedDeltaTime = NetworkManager.RealTimeProvider.DeltaTime; var cachedServerTime = serverTime.Time; - // TODO: Investigate Further + // 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. diff --git a/Editor/CodeGen/CodeGenHelpers.cs b/Editor/CodeGen/CodeGenHelpers.cs index 473e84f..1bb3ab1 100644 --- a/Editor/CodeGen/CodeGenHelpers.cs +++ b/Editor/CodeGen/CodeGenHelpers.cs @@ -59,7 +59,7 @@ public static uint Hash(this MethodDefinition methodDefinition) public static bool IsSubclassOf(this TypeDefinition typeDefinition, string classTypeFullName) { - if (!typeDefinition.IsClass) + if (typeDefinition == null || !typeDefinition.IsClass) { return false; } @@ -154,6 +154,10 @@ public static MethodReference MakeGeneric(this MethodReference self, params Type public static bool IsSubclassOf(this TypeReference typeReference, TypeReference baseClass) { + if (typeReference == null) + { + return false; + } var type = typeReference.Resolve(); if (type?.BaseType == null || type.BaseType.Name == nameof(Object)) { diff --git a/Editor/CodeGen/NetworkBehaviourILPP.cs b/Editor/CodeGen/NetworkBehaviourILPP.cs index b7ad88c..00a9e0a 100644 --- a/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -396,6 +396,8 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) #endif private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef; + private MethodReference m_RuntimeInitializeOnLoadAttribute_Ctor; + private MethodReference m_ExceptionCtorMethodReference; private MethodReference m_List_NetworkVariableBase_Add; @@ -509,6 +511,8 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) } } + m_RuntimeInitializeOnLoadAttribute_Ctor = moduleDefinition.ImportReference(typeof(RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new Type[] { })); + TypeDefinition networkManagerTypeDef = null; TypeDefinition networkBehaviourTypeDef = null; TypeDefinition networkVariableBaseTypeDef = null; @@ -1200,19 +1204,14 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass if (rpcHandlers.Count > 0 || rpcNames.Count > 0) { - var staticCtorMethodDef = typeDefinition.GetStaticConstructor(); - if (staticCtorMethodDef == null) - { - staticCtorMethodDef = new MethodDefinition( - ".cctor", // Static Constructor (constant-constructor) - MethodAttributes.HideBySig | - MethodAttributes.SpecialName | - MethodAttributes.RTSpecialName | + var staticCtorMethodDef = new MethodDefinition( + $"InitializeRPCS_{typeDefinition.Name}", + MethodAttributes.Assembly | MethodAttributes.Static, typeDefinition.Module.TypeSystem.Void); - staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); - typeDefinition.Methods.Add(staticCtorMethodDef); - } + staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + staticCtorMethodDef.CustomAttributes.Add(new CustomAttribute(m_RuntimeInitializeOnLoadAttribute_Ctor)); + typeDefinition.Methods.Add(staticCtorMethodDef); var instructions = new List(); var processor = staticCtorMethodDef.Body.GetILProcessor(); @@ -1254,7 +1253,8 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass baseGetTypeNameMethod.ReturnType) { ImplAttributes = baseGetTypeNameMethod.ImplAttributes, - SemanticsAttributes = baseGetTypeNameMethod.SemanticsAttributes + SemanticsAttributes = baseGetTypeNameMethod.SemanticsAttributes, + IsFamilyOrAssembly = true }; var processor = newGetTypeNameMethod.Body.GetILProcessor(); @@ -2225,6 +2225,12 @@ private void GenerateVariableInitialization(TypeDefinition type) } field = new FieldReference(fieldDefinition.Name, fieldDefinition.FieldType, genericType); } + + if (field.FieldType.Resolve() == null) + { + continue; + } + if (!field.FieldType.IsArray && !field.FieldType.Resolve().IsArray && field.FieldType.IsSubclassOf(m_NetworkVariableBase_TypeRef)) { // if({variable} == null) { diff --git a/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/Editor/CodeGen/RuntimeAccessModifiersILPP.cs index 7316664..51cf65a 100644 --- a/Editor/CodeGen/RuntimeAccessModifiersILPP.cs +++ b/Editor/CodeGen/RuntimeAccessModifiersILPP.cs @@ -98,6 +98,14 @@ private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assem fieldDefinition.IsPublic = true; } } + + foreach (var nestedTypeDefinition in typeDefinition.NestedTypes) + { + if (nestedTypeDefinition.Name == nameof(NetworkManager.RpcReceiveHandler)) + { + nestedTypeDefinition.IsNestedPublic = true; + } + } } private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) @@ -114,7 +122,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) { if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage) || fieldDefinition.Name == nameof(NetworkBehaviour.NetworkVariableFields)) { - fieldDefinition.IsFamily = true; + fieldDefinition.IsFamilyOrAssembly = true; } } @@ -130,6 +138,11 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) { methodDefinition.IsFamily = true; } + + if (methodDefinition.Name == nameof(NetworkBehaviour.__getTypeName)) + { + methodDefinition.IsFamilyOrAssembly = true; + } } } } diff --git a/Runtime/Connection/NetworkClient.cs b/Runtime/Connection/NetworkClient.cs index 8a73478..370f651 100644 --- a/Runtime/Connection/NetworkClient.cs +++ b/Runtime/Connection/NetworkClient.cs @@ -36,15 +36,11 @@ public class NetworkClient /// /// The ClientId of the NetworkClient /// - // TODO-2023-Q2: Determine if we want to make this property a public get and internal/private set - // There is no reason for a user to want to set this, but this will fail the package-validation-suite public ulong ClientId; /// /// The PlayerObject of the Client /// - // TODO-2023-Q2: Determine if we want to make this property a public get and internal/private set - // There is no reason for a user to want to set this, but this will fail the package-validation-suite public NetworkObject PlayerObject; /// diff --git a/Runtime/Connection/NetworkConnectionManager.cs b/Runtime/Connection/NetworkConnectionManager.cs index b59d99e..ccbe775 100644 --- a/Runtime/Connection/NetworkConnectionManager.cs +++ b/Runtime/Connection/NetworkConnectionManager.cs @@ -17,7 +17,6 @@ namespace Unity.Netcode /// - Processing s. /// - Client Disconnection /// - // TODO 2023-Q2: Discuss what kind of public API exposure we want for this public sealed class NetworkConnectionManager { #if DEVELOPMENT_BUILD || UNITY_EDITOR @@ -628,6 +627,8 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne }; if (!NetworkManager.NetworkConfig.EnableSceneManagement) { + // Update the observed spawned NetworkObjects for the newly connected player when scene management is disabled + NetworkManager.SpawnManager.UpdateObservedNetworkObjects(ownerClientId); if (NetworkManager.SpawnManager.SpawnedObjectsList.Count != 0) { message.SpawnedObjectsList = NetworkManager.SpawnManager.SpawnedObjectsList; @@ -651,12 +652,13 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); message.MessageVersions.Dispose(); - // If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization + // If scene management is disabled, then we are done and notify the local host-server the client is connected if (!NetworkManager.NetworkConfig.EnableSceneManagement) { + NetworkManager.ConnectedClients[ownerClientId].IsConnected = true; InvokeOnClientConnectedCallback(ownerClientId); } - else + else // Otherwise, let NetworkSceneManager handle the initial scene and NetworkObject synchronization { NetworkManager.SceneManager.SynchronizeNetworkObjects(ownerClientId); } @@ -665,6 +667,7 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne { LocalClient = client; NetworkManager.SpawnManager.UpdateObservedNetworkObjects(ownerClientId); + LocalClient.IsConnected = true; } if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkManager.NetworkConfig.PlayerPrefab == null)) @@ -730,12 +733,10 @@ internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash) internal NetworkClient AddClient(ulong clientId) { var networkClient = LocalClient; - if (clientId != NetworkManager.ServerClientId) - { - networkClient = new NetworkClient(); - networkClient.SetRole(isServer: false, isClient: true, NetworkManager); - networkClient.ClientId = clientId; - } + + networkClient = new NetworkClient(); + networkClient.SetRole(clientId == NetworkManager.ServerClientId, isClient: true, NetworkManager); + networkClient.ClientId = clientId; ConnectedClients.Add(clientId, networkClient); ConnectedClientsList.Add(networkClient); @@ -798,8 +799,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) } else { - // Handle changing ownership and prefab handlers - // TODO-2023: Look into whether in-scene placed NetworkObjects could be destroyed if ownership changes to a client + // Handle changing ownership and prefab handlers for (int i = clientOwnedObjects.Count - 1; i >= 0; i--) { var ownedObject = clientOwnedObjects[i]; diff --git a/Runtime/Core/NetworkBehaviour.cs b/Runtime/Core/NetworkBehaviour.cs index 782ecba..7a79bab 100644 --- a/Runtime/Core/NetworkBehaviour.cs +++ b/Runtime/Core/NetworkBehaviour.cs @@ -18,8 +18,6 @@ internal enum __RpcExecStage Server = 1, Client = 2 } - - // NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type internal virtual string __getTypeName() => nameof(NetworkBehaviour); @@ -98,7 +96,6 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth } bufferWriter.Dispose(); - #if DEVELOPMENT_BUILD || UNITY_EDITOR if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName)) { @@ -230,7 +227,6 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth } bufferWriter.Dispose(); - #if DEVELOPMENT_BUILD || UNITY_EDITOR if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName)) { diff --git a/Runtime/Core/NetworkBehaviourUpdater.cs b/Runtime/Core/NetworkBehaviourUpdater.cs index 0afe462..e6bcfc5 100644 --- a/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/Runtime/Core/NetworkBehaviourUpdater.cs @@ -118,7 +118,7 @@ internal void Shutdown() m_NetworkManager.NetworkTickSystem.Tick -= NetworkBehaviourUpdater_Tick; } - // TODO 2023-Q2: Order of operations requires NetworkVariable updates first then showing NetworkObjects + // Order of operations requires NetworkVariable updates first then showing NetworkObjects private void NetworkBehaviourUpdater_Tick() { // First update NetworkVariables diff --git a/Runtime/Core/NetworkManager.cs b/Runtime/Core/NetworkManager.cs index b08cb74..fb9e040 100644 --- a/Runtime/Core/NetworkManager.cs +++ b/Runtime/Core/NetworkManager.cs @@ -59,13 +59,12 @@ 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(); - // TODO 2023-Q2: Determine a better way to handle this + // TODO: Determine a better way to handle this NetworkObject.VerifyParentingStatus(); // 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(); - // TODO 2023-Q2: Determine a better way to handle this if (m_ShuttingDown) { ShutdownInternal(); @@ -834,9 +833,7 @@ public bool StartHost() } ConnectionManager.LocalClient.SetRole(true, true, this); - Initialize(true); - try { IsListening = NetworkConfig.NetworkTransport.StartServer(); @@ -942,10 +939,16 @@ public void Shutdown(bool discardMessageQueue = false) if (IsServer || IsClient) { m_ShuttingDown = true; - MessageManager.StopProcessing = discardMessageQueue; + if (MessageManager != null) + { + MessageManager.StopProcessing = discardMessageQueue; + } } - NetworkConfig.NetworkTransport.OnTransportEvent -= ConnectionManager.HandleNetworkEvent; + if (NetworkConfig != null && NetworkConfig.NetworkTransport != null) + { + NetworkConfig.NetworkTransport.OnTransportEvent -= ConnectionManager.HandleNetworkEvent; + } } // Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when unloading a scene with a NetworkManager @@ -1029,6 +1032,8 @@ internal void ShutdownInternal() // Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when quitting the application. private void OnApplicationQuit() { + // Make sure ShutdownInProgress returns true during this time + m_ShuttingDown = true; OnDestroy(); } diff --git a/Runtime/Core/NetworkObject.cs b/Runtime/Core/NetworkObject.cs index 3643d44..81542e3 100644 --- a/Runtime/Core/NetworkObject.cs +++ b/Runtime/Core/NetworkObject.cs @@ -733,6 +733,12 @@ internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays) /// Whether or not reparenting was successful. public bool TrySetParent(Transform parent, bool worldPositionStays = true) { + // If we are removing ourself from a parent + if (parent == null) + { + return TrySetParent((NetworkObject)null, worldPositionStays); + } + var networkObject = parent.GetComponent(); // If the parent doesn't have a NetworkObjet then return false, otherwise continue trying to parent @@ -1192,7 +1198,6 @@ internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) { NetworkLog.LogError($"{nameof(NetworkBehaviour)} index {index} was out of bounds for {name}. NetworkBehaviours must be the same, and in the same order, between server and client."); } - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { var currentKnownChildren = new System.Text.StringBuilder(); @@ -1205,7 +1210,6 @@ internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) } NetworkLog.LogInfo(currentKnownChildren.ToString()); } - return null; } diff --git a/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index 68e64cd..99970c4 100644 --- a/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -105,7 +105,6 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { networkVariable.WriteDelta(writer); } - NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent( TargetClientId, NetworkBehaviour.NetworkObject, @@ -207,7 +206,6 @@ public void Handle(ref NetworkContext context) networkBehaviour.__getTypeName(), context.MessageSize); - if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize)) diff --git a/Runtime/Messaging/Messages/RpcMessages.cs b/Runtime/Messaging/Messages/RpcMessages.cs index 1e91b06..59ecb9f 100644 --- a/Runtime/Messaging/Messages/RpcMessages.cs +++ b/Runtime/Messaging/Messages/RpcMessages.cs @@ -72,6 +72,14 @@ public static void Handle(ref NetworkContext context, ref RpcMetadata metadata, catch (Exception ex) { Debug.LogException(new Exception("Unhandled RPC exception!", ex)); + if (networkManager.LogLevel == LogLevel.Developer) + { + Debug.Log($"RPC Table Contents"); + foreach (var entry in NetworkManager.__rpc_func_table) + { + Debug.Log($"{entry.Key} | {entry.Value.Method.Name}"); + } + } } } } diff --git a/Runtime/SceneManagement/NetworkSceneManager.cs b/Runtime/SceneManagement/NetworkSceneManager.cs index 91fd876..f7a2f13 100644 --- a/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2191,6 +2191,10 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) ClientId = clientId }); + // At this point the client is considered fully "connected" + NetworkManager.ConnectedClients[clientId].IsConnected = true; + + // All scenes are synchronized, let the server know we are done synchronizing OnSynchronizeComplete?.Invoke(clientId); // At this time the client is fully synchronized with all loaded scenes and diff --git a/Runtime/Spawning/NetworkSpawnManager.cs b/Runtime/Spawning/NetworkSpawnManager.cs index a82aa6b..c7ddc60 100644 --- a/Runtime/Spawning/NetworkSpawnManager.cs +++ b/Runtime/Spawning/NetworkSpawnManager.cs @@ -113,12 +113,6 @@ internal void UpdateOwnershipTable(NetworkObject networkObject, ulong newOwner, // Remove the previous owner's entry OwnershipToObjectsTable[previousOwner].Remove(networkObject.NetworkObjectId); - // Server or Host alway invokes the lost ownership notification locally - if (NetworkManager.IsServer) - { - networkObject.InvokeBehaviourOnLostOwnership(); - } - // If we are removing the entry (i.e. despawning or client lost ownership) if (isRemoving) { @@ -143,12 +137,6 @@ internal void UpdateOwnershipTable(NetworkObject networkObject, ulong newOwner, { // Add the new ownership entry OwnershipToObjectsTable[newOwner].Add(networkObject.NetworkObjectId, networkObject); - - // Server or Host always invokes the gained ownership notification locally - if (NetworkManager.IsServer) - { - networkObject.InvokeBehaviourOnGainedOwnership(); - } } else if (isRemoving) { @@ -227,43 +215,6 @@ public NetworkObject GetPlayerNetworkObject(ulong clientId) return null; } - internal void RemoveOwnership(NetworkObject networkObject) - { - if (!NetworkManager.IsServer) - { - throw new NotServerException("Only the server can change ownership"); - } - - if (!networkObject.IsSpawned) - { - throw new SpawnStateException("Object is not spawned"); - } - - // If we made it here then we are the server and if the server is determined to already be the owner - // then ignore the RemoveOwnership invocation. - if (networkObject.OwnerClientId == NetworkManager.ServerClientId) - { - return; - } - - networkObject.OwnerClientId = NetworkManager.ServerClientId; - - // Server removes the entry and takes over ownership before notifying - UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true); - - var message = new ChangeOwnershipMessage - { - NetworkObjectId = networkObject.NetworkObjectId, - OwnerClientId = networkObject.OwnerClientId - }; - var size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds); - - foreach (var client in NetworkManager.ConnectedClients) - { - NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); - } - } - /// /// Helper function to get a network client for a clientId from the NetworkManager. /// On the server this will check the list. @@ -289,6 +240,11 @@ private bool TryGetNetworkClient(ulong clientId, out NetworkClient networkClient return false; } + internal void RemoveOwnership(NetworkObject networkObject) + { + ChangeOwnership(networkObject, NetworkManager.ServerClientId); + } + internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) { if (!NetworkManager.IsServer) @@ -301,14 +257,21 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) throw new SpawnStateException("Object is not spawned"); } + // Assign the new owner networkObject.OwnerClientId = clientId; + // Always notify locally on the server when ownership is lost + networkObject.InvokeBehaviourOnLostOwnership(); + networkObject.MarkVariablesDirty(true); NetworkManager.BehaviourUpdater.AddForUpdate(networkObject); // Server adds entries for all client ownership UpdateOwnershipTable(networkObject, networkObject.OwnerClientId); + // Always notify locally on the server when a new owner is assigned + networkObject.InvokeBehaviourOnGainedOwnership(); + var message = new ChangeOwnershipMessage { NetworkObjectId = networkObject.NetworkObjectId, @@ -952,27 +915,35 @@ internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObjec } /// - /// Updates all spawned for the specified 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 /// + /// + /// This method is to only to be used for newly connected clients in order to update the observers list for + /// each NetworkObject instance. + /// internal void UpdateObservedNetworkObjects(ulong clientId) { foreach (var sobj in SpawnedObjectsList) { + // If the NetworkObject has no visibility check then prepare to add this client as an observer if (sobj.CheckObjectVisibility == null) { - if (!sobj.Observers.Contains(clientId)) + // 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.Observers.Contains(clientId) && (sobj.SpawnWithObservers || clientId == NetworkManager.ServerClientId)) { sobj.Observers.Add(clientId); } } else { + // CheckObject visibility overrides SpawnWithObservers under this condition if (sobj.CheckObjectVisibility(clientId)) { sobj.Observers.Add(clientId); } - else if (sobj.Observers.Contains(clientId)) + else // Otherwise, if the observers contains the clientId (shouldn't happen) then remove it since CheckObjectVisibility returned false + if (sobj.Observers.Contains(clientId)) { sobj.Observers.Remove(clientId); } diff --git a/Runtime/Timing/NetworkTimeSystem.cs b/Runtime/Timing/NetworkTimeSystem.cs index f31746b..16a3c4e 100644 --- a/Runtime/Timing/NetworkTimeSystem.cs +++ b/Runtime/Timing/NetworkTimeSystem.cs @@ -9,9 +9,6 @@ namespace Unity.Netcode /// public class NetworkTimeSystem { - /// - /// TODO 2023-Q2: Not sure if this just needs to go away, but there is nothing that ever replaces this - /// /// /// This was the original comment when it lived in NetworkManager: /// todo talk with UX/Product, find good default value for this diff --git a/Tests/Editor/Transports/UnityTransportTests.cs b/Tests/Editor/Transports/UnityTransportTests.cs index 95f57f2..fb61c05 100644 --- a/Tests/Editor/Transports/UnityTransportTests.cs +++ b/Tests/Editor/Transports/UnityTransportTests.cs @@ -121,9 +121,7 @@ public void UnityTransport_RestartSucceedsAfterFailure() LogAssert.Expect(LogType.Error, "Invalid network endpoint: 127.0.0.:4242."); LogAssert.Expect(LogType.Error, "Network listen address (127.0.0.) is Invalid!"); -#if UTP_TRANSPORT_2_0_ABOVE - LogAssert.Expect(LogType.Error, "Socket creation failed (error Unity.Baselib.LowLevel.Binding+Baselib_ErrorState: Invalid argument (0x01000003) "); -#endif + transport.SetConnectionData("127.0.0.1", 4242, "127.0.0.1"); Assert.True(transport.StartServer()); diff --git a/Tests/Runtime/NetworkBehaviourGenericTests.cs b/Tests/Runtime/NetworkBehaviourGenericTests.cs index cea941a..ab48aaa 100644 --- a/Tests/Runtime/NetworkBehaviourGenericTests.cs +++ b/Tests/Runtime/NetworkBehaviourGenericTests.cs @@ -67,7 +67,6 @@ public IEnumerator ValidatedDisableddNetworkBehaviourWarning() // Set the child object to be inactive in the hierarchy childObject.SetActive(false); - LogAssert.Expect(LogType.Warning, $"{childObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during ownership assignment!"); LogAssert.Expect(LogType.Warning, $"{childObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during spawn!"); parentNetworkObject.Spawn(); diff --git a/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index 87ec806..ee01794 100644 --- a/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -61,8 +61,26 @@ private bool CheckClientsSideObserverTestObj() return true; } + /// + /// Assures the late joining client has all + /// NetworkPrefabs required to connect. + /// + 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); + } + } + base.OnNewClientCreated(networkManager); + } - + /// + /// This test validates property + /// + /// whether to spawn with or without observers [UnityTest] public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTypes) { @@ -92,6 +110,23 @@ public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTyp m_ObserverTestType = ObserverTestTypes.WithObservers; yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj); AssertOnTimeout($"{k_WithObserversError} {k_ObserverTestObjName} object!"); + + // Validate that a late joining client does not see the NetworkObject when it spawns + yield return CreateAndStartNewClient(); + + 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!"); + + // Now validate that we can make the NetworkObject visible to the newly joined client + m_ObserverTestNetworkObject.NetworkShow(m_ClientNetworkManagers[NumberOfClients].LocalClientId); + + // Validate the NetworkObject is visible to all connected clients (including the recently joined client) + m_ObserverTestType = ObserverTestTypes.WithObservers; + yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj); + AssertOnTimeout($"{k_WithObserversError} {k_ObserverTestObjName} object!"); } } /// diff --git a/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs index 1f4e3c7..5fa4915 100644 --- a/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs +++ b/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs @@ -42,6 +42,12 @@ public class NetworkObjectOwnershipTests : NetcodeIntegrationTest public NetworkObjectOwnershipTests(HostOrServer hostOrServer) : base(hostOrServer) { } + public enum OwnershipChecks + { + Change, + Remove + } + protected override void OnServerAndClientsCreated() { m_OwnershipPrefab = CreateNetworkObjectPrefab("OnwershipPrefab"); @@ -62,7 +68,7 @@ public void TestPlayerIsOwned() } [UnityTest] - public IEnumerator TestOwnershipCallbacks() + public IEnumerator TestOwnershipCallbacks([Values] OwnershipChecks ownershipChecks) { m_OwnershipObject = SpawnObject(m_OwnershipPrefab, m_ServerNetworkManager); m_OwnershipNetworkObject = m_OwnershipObject.GetComponent(); @@ -109,7 +115,17 @@ public IEnumerator TestOwnershipCallbacks() serverComponent.ResetFlags(); clientComponent.ResetFlags(); - serverObject.ChangeOwnership(NetworkManager.ServerClientId); + if (ownershipChecks == OwnershipChecks.Change) + { + // Validates that when ownership is changed back to the server it will get an OnGainedOwnership notification + serverObject.ChangeOwnership(NetworkManager.ServerClientId); + } + else + { + // Validates that when ownership is removed the server gets an OnGainedOwnership notification + serverObject.RemoveOwnership(); + } + yield return s_DefaultWaitForTick; Assert.That(serverComponent.OnGainedOwnershipFired); @@ -125,7 +141,7 @@ public IEnumerator TestOwnershipCallbacks() /// Verifies that switching ownership between several clients works properly /// [UnityTest] - public IEnumerator TestOwnershipCallbacksSeveralClients() + public IEnumerator TestOwnershipCallbacksSeveralClients([Values] OwnershipChecks ownershipChecks) { // Build our message hook entries tables so we can determine if all clients received spawn or ownership messages var messageHookEntriesForSpawn = new List(); @@ -247,8 +263,17 @@ bool WaitForClientsToSpawnNetworkObject() previousClientComponent = currentClientComponent; } - // Now change ownership back to the server - serverObject.ChangeOwnership(NetworkManager.ServerClientId); + if (ownershipChecks == OwnershipChecks.Change) + { + // Validates that when ownership is changed back to the server it will get an OnGainedOwnership notification + serverObject.ChangeOwnership(NetworkManager.ServerClientId); + } + else + { + // Validates that when ownership is removed the server gets an OnGainedOwnership notification + serverObject.RemoveOwnership(); + } + yield return WaitForConditionOrTimeOut(ownershipMessageHooks); Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for all clients to receive the {nameof(ChangeOwnershipMessage)} message (back to server)."); @@ -269,5 +294,69 @@ bool WaitForClientsToSpawnNetworkObject() } serverComponent.ResetFlags(); } + + private const int k_NumberOfSpawnedObjects = 5; + + private bool AllClientsHaveCorrectObjectCount() + { + + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + if (clientNetworkManager.LocalClient.OwnedObjects.Count < k_NumberOfSpawnedObjects) + { + return false; + } + } + + return true; + } + + private bool ServerHasCorrectClientOwnedObjectCount() + { + // Only check when we are the host + if (m_ServerNetworkManager.IsHost) + { + if (m_ServerNetworkManager.LocalClient.OwnedObjects.Count < k_NumberOfSpawnedObjects) + { + return false; + } + } + + foreach (var connectedClient in m_ServerNetworkManager.ConnectedClients) + { + if (connectedClient.Value.OwnedObjects.Count < k_NumberOfSpawnedObjects) + { + return false; + } + } + return true; + } + + [UnityTest] + public IEnumerator TestOwnedObjectCounts() + { + if (m_ServerNetworkManager.IsHost) + { + for (int i = 0; i < 5; i++) + { + SpawnObject(m_OwnershipPrefab, m_ServerNetworkManager); + } + } + + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + for (int i = 0; i < 5; i++) + { + SpawnObject(m_OwnershipPrefab, clientNetworkManager); + } + } + + yield return WaitForConditionOrTimeOut(AllClientsHaveCorrectObjectCount); + AssertOnTimeout($"Not all clients spawned {k_NumberOfSpawnedObjects} {nameof(NetworkObject)}s!"); + + yield return WaitForConditionOrTimeOut(ServerHasCorrectClientOwnedObjectCount); + AssertOnTimeout($"Server does not have the correct count for all clients spawned {k_NumberOfSpawnedObjects} {nameof(NetworkObject)}s!"); + + } } } diff --git a/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 0f246b2..abd0c42 100644 --- a/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Text; using NUnit.Framework; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; @@ -378,7 +379,7 @@ private void AllChildrenLocalTransformValuesMatch() { var success = WaitForConditionOrTimeOutWithTimeTravel(AllInstancesKeptLocalTransformValues); //TimeTravelToNextTick(); - var infoMessage = new System.Text.StringBuilder($"Timed out waiting for all children to have the correct local space values:\n"); + var infoMessage = new StringBuilder($"Timed out waiting for all children to have the correct local space values:\n"); var authorityObjectLocalPosition = m_AuthorityChildObject.transform.localPosition; var authorityObjectLocalRotation = m_AuthorityChildObject.transform.localRotation.eulerAngles; var authorityObjectLocalScale = m_AuthorityChildObject.transform.localScale; @@ -567,9 +568,9 @@ private void WaitForNextTick() } } - // The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime - // Note: this was reduced from 8 iterations to 3 due to the number of tests based on all of the various parameter combinations + // The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime private const int k_PositionRotationScaleIterations = 3; + private const int k_PositionRotationScaleIterations3Axis = 8; protected override void OnNewClientCreated(NetworkManager networkManager) { @@ -594,22 +595,69 @@ protected override float GetDeltaVarianceThreshold() private Axis m_CurrentAxis; + + private bool m_AxisExcluded; + + /// + /// Randomly determine if an axis should be excluded. + /// If so, then randomly pick one of the axis to be excluded. + /// + private Vector3 RandomlyExcludeAxis(Vector3 delta) + { + if (Random.Range(0.0f, 1.0f) >= 0.5f) + { + m_AxisExcluded = true; + var axisToIgnore = Random.Range(0, 2); + switch (axisToIgnore) + { + case 0: + { + delta.x = 0; + break; + } + case 1: + { + delta.y = 0; + break; + } + case 2: + { + delta.z = 0; + break; + } + } + } + return delta; + } + /// /// 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. + /// [Test] public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] OverrideState overideState, [Values] Precision precision, [Values] Rotation rotationSynch, [Values] Axis axis) { - // In the name of reducing the very long time it takes to interpolate and run all of the possible combinations, - // we only interpolate when the second client joins - m_AuthoritativeTransform.Interpolate = false; 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; + + // 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 = precision == Precision.Half; @@ -640,29 +688,49 @@ public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace test 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; + 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 WaitForNextTick(); + 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 < k_PositionRotationScaleIterations; i++) + 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; - position = positionStart * i; - rotation = rotationStart * i; - scale = scaleStart * i; + + // 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); @@ -670,54 +738,37 @@ public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace test // Wait for the deltas to be pushed 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})!"); - // Wait for deltas to synchronize on non-authoritative side - var success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches); - // Provide additional debug info about what failed (if it fails) - if (!success) + // 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) { - m_EnableVerboseDebug = true; - PositionRotationScaleMatches(); - m_EnableVerboseDebug = false; + // Wait for deltas to synchronize on non-authoritative side + success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches); + // Provide additional debug info about what failed (if it fails) + if (!success) + { + m_EnableVerboseDebug = true; + success = PositionRotationScaleMatches(); + m_EnableVerboseDebug = false; + } + Assert.True(success, $"[Non-Interpolate {i}] Timed out waiting for non-authority to match authority's position or rotation"); } - Assert.True(success, $"[Non-Interpolate {i}] Timed out waiting for non-authority to match authority's position or rotation"); } - // Only enable interpolation when all axis are set (to reduce the test times) - if (axis == Axis.XYZ) + if (axisCount == 3) { - // Now, enable interpolation - m_AuthoritativeTransform.Interpolate = true; - m_NonAuthoritativeTransform.StateUpdated = false; - m_AuthoritativeTransform.StatePushed = false; - // Wait for the delta (change in interpolation) to be pushed - var success = WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed && m_NonAuthoritativeTransform.StateUpdated); - Assert.True(success, $"[Interpolation Enable] Timed out waiting for state to be pushed ({m_AuthoritativeTransform.StatePushed}) or state to be updated ({m_NonAuthoritativeTransform.StateUpdated})!"); - - // Continue for one more update with interpolation enabled - // Note: We are just verifying one update with interpolation enabled due to the number of tests this integration test has to run - // and since the NestedNetworkTransformTests already tests interpolation under the same number of conditions (excluding Axis). - // This is just to verify selecting specific axis doesn't cause issues when interpolating as well. - m_NonAuthoritativeTransform.StateUpdated = false; - m_AuthoritativeTransform.StatePushed = false; - position = positionStart * k_PositionRotationScaleIterations; - rotation = rotationStart * k_PositionRotationScaleIterations; - scale = scaleStart * k_PositionRotationScaleIterations; - MoveRotateAndScaleAuthority(position, rotation, scale, overideState); - - // Wait for the deltas to be pushed and updated - success = WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed && m_NonAuthoritativeTransform.StateUpdated); - Assert.True(success, $"[Interpolation {k_PositionRotationScaleIterations}] Timed out waiting for state to be pushed ({m_AuthoritativeTransform.StatePushed}) or state to be updated ({m_NonAuthoritativeTransform.StateUpdated})!"); - - success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches, 120); - + // As a final test, wait for deltas to synchronize on non-authoritative side to assure it interpolates to th + success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches); // Provide additional debug info about what failed (if it fails) if (!success) { m_EnableVerboseDebug = true; - PositionRotationScaleMatches(); + success = PositionRotationScaleMatches(); m_EnableVerboseDebug = false; } - Assert.True(success, $"[Interpolation {k_PositionRotationScaleIterations}] Timed out waiting for non-authority to match authority's position or rotation"); + Assert.True(success, $"Timed out waiting for non-authority to match authority's position or rotation"); } } diff --git a/ValidationExceptions.json b/ValidationExceptions.json new file mode 100644 index 0000000..2d81612 --- /dev/null +++ b/ValidationExceptions.json @@ -0,0 +1,10 @@ +{ + "ErrorExceptions": [ + { + "ValidationTest": "API Validation", + "ExceptionMessage": "Additions require a new minor or major version.", + "PackageVersion": "1.5.2" + } + ], + "WarningExceptions": [] +} \ No newline at end of file diff --git a/ValidationExceptions.json.meta b/ValidationExceptions.json.meta new file mode 100644 index 0000000..3316cf2 --- /dev/null +++ b/ValidationExceptions.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2a43005be301c9043aab7034757d4868 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json index f5e58ad..94c0ebd 100644 --- a/package.json +++ b/package.json @@ -2,23 +2,23 @@ "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.5.1", + "version": "1.5.2", "unity": "2020.3", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", "com.unity.transport": "1.3.4" }, "_upm": { - "changelog": "### Added\n\n- Added support for serializing `NativeArray<>` and `NativeList<>` in `FastBufferReader`/`FastBufferWriter`, `BufferSerializer`, `NetworkVariable`, and RPCs. (To use `NativeList<>`, add `UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT` to your Scripting Define Symbols in `Project Settings > Player`) (#2375)\n- The location of the automatically-created default network prefab list can now be configured (#2544)\n- Added: Message size limits (max single message and max fragmented message) can now be set using NetworkManager.MaximumTransmissionUnitSize and NetworkManager.MaximumFragmentedMessageSize for transports that don't work with the default values (#2530)\n- Added `NetworkObject.SpawnWithObservers` property (default is true) that when set to false will spawn a `NetworkObject` with no observers and will not be spawned on any client until `NetworkObject.NetworkShow` is invoked. (#2568)\n\n### Fixed\n\n- Fixed: Fixed a null reference in codegen in some projects (#2581)\n- Fixed issue where the `OnClientDisconnected` client identifier was incorrect after a pending client connection was denied. (#2569)\n- Fixed warning \"Runtime Network Prefabs was not empty at initialization time.\" being erroneously logged when no runtime network prefabs had been added (#2565)\n- Fixed issue where some temporary debug console logging was left in a merged PR. (#2562)\n- Fixed the \"Generate Default Network Prefabs List\" setting not loading correctly and always reverting to being checked. (#2545)\n- Fixed issue where users could not use NetworkSceneManager.VerifySceneBeforeLoading to exclude runtime generated scenes from client synchronization. (#2550)\n- Fixed missing value on `NetworkListEvent` for `EventType.RemoveAt` events. (#2542,#2543)\n- Fixed issue where parenting a NetworkTransform under a transform with a scale other than Vector3.one would result in incorrect values on non-authoritative instances. (#2538)\n- Fixed issue where a server would include scene migrated and then despawned NetworkObjects to a client that was being synchronized. (#2532)\n- Fixed the inspector throwing exceptions when attempting to render `NetworkVariable`s of enum types. (#2529)\n- Making a `NetworkVariable` with an `INetworkSerializable` type that doesn't meet the `new()` constraint will now create a compile-time error instead of an editor crash (#2528)\n- Fixed Multiplayer Tools package installation docs page link on the NetworkManager popup. (#2526)\n- Fixed an exception and error logging when two different objects are shown and hidden on the same frame (#2524)\n- Fixed a memory leak in `UnityTransport` that occurred if `StartClient` failed. (#2518)\n- Fixed issue where a client could throw an exception if abruptly disconnected from a network session with one or more spawned `NetworkObject`(s). (#2510)\n- Fixed issue where invalid endpoint addresses were not being detected and returning false from NGO UnityTransport. (#2496)\n- Fixed some errors that could occur if a connection is lost and the loss is detected when attempting to write to the socket. (#2495)\n\n## Changed\n\n- Adding network prefabs before NetworkManager initialization is now supported. (#2565)\n- Connecting clients being synchronized now switch to the server's active scene before spawning and synchronizing NetworkObjects. (#2532)\n- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.4. (#2533)\n- Improved performance of NetworkBehaviour initialization by replacing reflection when initializing NetworkVariables with compile-time code generation, which should help reduce hitching during additive scene loads. (#2522)" + "changelog": "### Added\n\n### Fixed\n\n- Fixed issue where `NetworkClient.OwnedObjects` was not returning any owned objects due to the `NetworkClient.IsConnected` not being properly set. (#2631)\n- Fixed a crash when calling TrySetParent with a null Transform (#2625)\n- Fixed issue where a `NetworkTransform` using full precision state updates was losing transform state updates when interpolation was enabled. (#2624)\n- Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored for late joining clients. (#2623)\n- Fixed issue where invoking `NetworkManager.Shutdown` multiple times, depending upon the timing, could cause an exception. (#2622)\n- Fixed issue where removing ownership would not notify the server that it gained ownership. This also resolves the issue where an owner authoritative NetworkTransform would not properly initialize upon removing ownership from a remote client. (#2618)\n- Fixed ILPP issues when using CoreCLR and for certain dedicated server builds. (#2614)\n- Fixed an ILPP compile error when creating a generic NetworkBehaviour singleton with a static T instance. (#2603)\n\n### Changed" }, "upmCi": { - "footprint": "35c5325acc3edf18c37ef8c9d19e0944fae0d42a" + "footprint": "e7549ba358ade416ab85285cdf53c5a6aac35cef" }, "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@1.5/manual/index.html", "repository": { "url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git", "type": "git", - "revision": "7a969f89d6dda65ac373ce552c0c997c9116f21a" + "revision": "36368846c5bfe6cfb93adc36282507614955955c" }, "samples": [ {