From fb8a5cd576b0d20df639b38da81a22841281b1ab Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 8 Sep 2023 17:48:38 -0500 Subject: [PATCH 01/12] update Adding OnOwnershipChanged(ulong previous, ulong current) --- .../Runtime/Core/NetworkBehaviour.cs | 17 +++++++++++++++++ .../Runtime/Core/NetworkObject.cs | 15 +++++++++++++++ .../Messages/ChangeOwnershipMessage.cs | 2 ++ .../Runtime/Spawning/NetworkSpawnManager.cs | 7 +++++++ 4 files changed, 41 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 7a79babb67..662824c01d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -532,6 +532,23 @@ internal void InternalOnGainedOwnership() OnGainedOwnership(); } + /// + /// 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). + /// + /// the previous owner + /// the current owner + protected virtual void OnOwnershipChanged(ulong previous, ulong current) + { + + } + + internal void InternalOnOwnershipChanged(ulong previous, ulong current) + { + OnOwnershipChanged(previous, current); + } + /// /// Gets called when we loose ownership of this object /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 64bf640627..d79d922ea9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -753,6 +753,21 @@ internal void InvokeBehaviourOnGainedOwnership() } } + internal void InvokeOwnershipChanged(ulong previous, ulong next) + { + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) + { + if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) + { + ChildNetworkBehaviours[i].InternalOnOwnershipChanged(previous, next); + } + else + { + Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!"); + } + } + } + internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { for (int i = 0; i < ChildNetworkBehaviours.Count; i++) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index d52de3b775..b0d1ca7c4d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -60,6 +60,8 @@ public void Handle(ref NetworkContext context) } } + networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId); + networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index c7ddc60a4a..c11b487b7d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -257,6 +257,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) throw new SpawnStateException("Object is not spawned"); } + var previous = networkObject.OwnerClientId; // Assign the new owner networkObject.OwnerClientId = clientId; @@ -286,6 +287,12 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); } } + + // 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 + networkObject.InvokeOwnershipChanged(previous, clientId); } internal bool HasPrefab(NetworkObject.SceneObject sceneObject) From 528aa1d5429a67f7fd86e377242cc1840966ee71 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 8 Sep 2023 18:35:47 -0500 Subject: [PATCH 02/12] fix This resolves the issue with client authoritative network transforms and the random "noise" that would occur when transitioning ownership from a remote client back to the host-server. - Latent messages from the client would still be received and processed after ownership changed. - Ownership changed messages would proceed the NetworkTransform initialization state update message. Now ownership changed messages precede the NetworkTransform initialization state update message. - Clients could sometimes have the same network tick value even when the tick event had triggered, which for NetworkDeltaPosition would cause dropped state updates. --- .../Components/NetworkTransform.cs | 75 +++++++++++++------ 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index ab88ddef9a..8fd2660189 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -499,6 +499,10 @@ 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 + /// which will not be the full position. + /// To get a NettworkTransform's full position, use and + /// pass true as the parameter. /// /// public Vector3 GetPosition() @@ -1110,7 +1114,16 @@ public Vector3 GetSpaceRelativePosition(bool getCurrentState = false) } else { - return m_CurrentPosition; + // When half float precision is enabled, get the NetworkDeltaPosition's full position + if (UseHalfFloatPrecision) + { + return m_HalfPositionState.GetFullPosition(); + } + else + { + // Otherwise, just get the current position + return m_CurrentPosition; + } } } @@ -1393,6 +1406,8 @@ protected virtual void OnAuthorityPushTransformState(ref NetworkTransformState n { } + // Tracks the last tick a state update was sent (see further below) + private int m_LastTick; /// /// Authoritative side only /// If there are any transform delta states, this method will synchronize the @@ -1411,11 +1426,27 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz if (ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize)) { m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OldState.LastSerializedSize; - OnAuthorityPushTransformState(ref m_LocalAuthoritativeNetworkState); + // Make sure our network tick is incremented + if (m_LastTick == m_LocalAuthoritativeNetworkState.NetworkTick && !m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame) + { + // When running in authority and a remote client is the owner, the client can hit a perfect window of time where + // it is still on the previous network tick (as a count) but still have had the tick event triggered. + // (This is cheaper than calculating the exact tick each time and only can occur on clients) + if (!IsServer) + { + m_LocalAuthoritativeNetworkState.NetworkTick = m_LocalAuthoritativeNetworkState.NetworkTick + 1; + } + else + { + NetworkLog.LogError($"[NT TICK DUPLICATE] Server already sent an update on tick {m_LastTick} and is attempting to send again on the same network tick!"); + } + } + m_LastTick = m_LocalAuthoritativeNetworkState.NetworkTick; // Update the state UpdateTransformState(); + OnAuthorityPushTransformState(ref m_LocalAuthoritativeNetworkState); m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false; } } @@ -2209,6 +2240,7 @@ private void ApplyUpdatedState(NetworkTransformState newState) m_HalfPositionState.HalfVector3.Axis = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.HalfVector3.Axis; // and update our target position m_TargetPosition = m_HalfPositionState.ToVector3(newState.NetworkTick); + m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.CurrentBasePosition = m_HalfPositionState.CurrentBasePosition; m_LocalAuthoritativeNetworkState.CurrentPosition = m_TargetPosition; } @@ -2455,6 +2487,9 @@ private void NetworkTickSystem_Tick() // Update any changes to the transform var transformSource = transform; OnUpdateAuthoritativeState(ref transformSource); + + m_CurrentPosition = GetSpaceRelativePosition(); + m_TargetPosition = GetSpaceRelativePosition(); } else { @@ -2466,9 +2501,6 @@ private void NetworkTickSystem_Tick() } } - - - /// public override void OnNetworkSpawn() { @@ -2509,25 +2541,14 @@ public override void OnDestroy() base.OnDestroy(); } - /// - public override void OnGainedOwnership() - { - // Only initialize if we gained ownership - if (OwnerClientId == NetworkManager.LocalClientId) - { - Initialize(); - } - } - - /// - public override void OnLostOwnership() + protected override void OnOwnershipChanged(ulong previous, ulong current) { - // Only initialize if we are not authority and lost - // ownership - if (OwnerClientId != NetworkManager.LocalClientId) + // If we were the previous owner or the newly assigned owner then reinitialize + if (current == NetworkManager.LocalClientId || previous == NetworkManager.LocalClientId) { Initialize(); } + base.OnOwnershipChanged(previous, current); } /// @@ -2552,6 +2573,7 @@ protected virtual void OnInitialize(ref NetworkVariable r } + /// /// Initializes NetworkTransform when spawned and ownership changes. /// @@ -2572,7 +2594,8 @@ protected void Initialize() { m_HalfPositionState = new NetworkDeltaPosition(currentPosition, NetworkManager.NetworkTickSystem.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); } - + m_CurrentPosition = currentPosition; + m_TargetPosition = currentPosition; // Authority only updates once per network tick NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick; NetworkManager.NetworkTickSystem.Tick += NetworkTickSystem_Tick; @@ -2835,6 +2858,13 @@ public bool IsServerAuthoritative() /// serialzied private void TransformStateUpdate(ulong senderId, FastBufferReader messagePayload) { + if (!OnIsServerAuthoritative() && IsServer && OwnerClientId == NetworkManager.ServerClientId) + { + // Ownership must have changed, ignore any additional pending messages that might have + // come from a previous owner client. + return; + } + // Forward owner authoritative messages before doing anything else if (IsServer && !OnIsServerAuthoritative()) { @@ -2856,6 +2886,7 @@ private void TransformStateUpdate(ulong senderId, FastBufferReader messagePayloa /// the owner state message payload private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload) { + var serverAuthoritative = OnIsServerAuthoritative(); var currentPosition = messagePayload.Position; var messageSize = messagePayload.Length - currentPosition; var writer = new FastBufferWriter(messageSize, Allocator.Temp); @@ -2867,7 +2898,7 @@ private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload) for (int i = 0; i < clientCount; i++) { var clientId = NetworkManager.ConnectionManager.ConnectedClientsList[i].ClientId; - if (!OnIsServerAuthoritative() && (NetworkManager.ServerClientId == clientId || clientId == OwnerClientId)) + if (NetworkManager.ServerClientId == clientId || (!serverAuthoritative && clientId == OwnerClientId)) { continue; } From 71956b5b9463bcfe56f44add45f786095cfbaea7 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 8 Sep 2023 20:33:26 -0500 Subject: [PATCH 03/12] test minor adjustment to a test that would fail from time to time due to precision. --- .../Tests/Runtime/NetworkTransform/NetworkTransformTests.cs | 3 ++- 1 file changed, 2 insertions(+), 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 321f9137df..a29c9630e6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -588,6 +588,7 @@ public void ParentedNetworkTransformTest([Values] Precision precision, [Values] success = WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesHaveChild); Assert.True(success, "Timed out waiting for all instances to have parented a child!"); + TimeTravelToNextTick(); // This validates each child instance has preserved their local space values AllChildrenLocalTransformValuesMatch(false); @@ -689,7 +690,7 @@ protected override void OnNewClientCreated(NetworkManager networkManager) private Precision m_Precision = Precision.Full; private float m_CurrentHalfPrecision = 0.0f; - private const float k_HalfPrecisionPosScale = 0.03f; + private const float k_HalfPrecisionPosScale = 0.041f; private const float k_HalfPrecisionRot = 0.725f; protected override float GetDeltaVarianceThreshold() From ac23079dd4c71c351b4b448576ddf9aaca9b9759 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 9 Sep 2023 02:03:00 -0500 Subject: [PATCH 04/12] fix - validation bug This is another bug in the validation test suite where removing an override of a virtual method that can still be overridden will throw an API validation error. --- .../Components/NetworkTransform.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 8fd2660189..2e0b8d88d8 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -2541,6 +2541,18 @@ public override void OnDestroy() base.OnDestroy(); } + /// + 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 From 83002685d56214f27dcce3e6f94b8793f1778754 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 15 Sep 2023 17:59:09 -0500 Subject: [PATCH 05/12] update Send deltas unreliable but synchronization and teleporting reliably. --- .../Components/NetworkDeltaPosition.cs | 7 + .../Components/NetworkTransform.cs | 159 ++++++++++++++---- 2 files changed, 134 insertions(+), 32 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs b/com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs index a7ed563106..df21e62d8a 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs @@ -23,12 +23,18 @@ public struct NetworkDeltaPosition : INetworkSerializable internal Vector3 DeltaPosition; internal int NetworkTick; + internal bool SynchronizeBase; + /// /// The serialization implementation of /// public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { HalfVector3.NetworkSerialize(serializer); + if (SynchronizeBase) + { + serializer.SerializeValue(ref CurrentBasePosition); + } } /// @@ -164,6 +170,7 @@ public NetworkDeltaPosition(Vector3 vector3, int networkTick, bool3 axisToSynchr DeltaPosition = Vector3.zero; HalfDeltaConvertedBack = Vector3.zero; HalfVector3 = new HalfVector3(vector3, axisToSynchronize); + SynchronizeBase = false; UpdateFrom(ref vector3, networkTick); } diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 2e0b8d88d8..bb51cb6064 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -78,6 +78,8 @@ public struct NetworkTransformState : INetworkSerializable 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 + private const int k_SynchBaseHalfFloat = 0x00040000; + private const int k_ReliableFragmentedSequenced = 0x00080000; private const int k_TrackStateId = 0x10000000; // (Internal Debugging) When set each state update will contain a state identifier // Stores persistent and state relative flags @@ -438,6 +440,24 @@ internal bool IsParented } } + internal bool SynchronizeBaseHalfFloat + { + get => GetFlag(k_SynchBaseHalfFloat); + set + { + SetFlag(value, k_SynchBaseHalfFloat); + } + } + + internal bool ReliableFragmentedSequenced + { + get => GetFlag(k_ReliableFragmentedSequenced); + set + { + SetFlag(value, k_ReliableFragmentedSequenced); + } + } + internal bool TrackByStateId { get => GetFlag(k_TrackStateId); @@ -594,6 +614,15 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { if (isWriting) { + if (IsTeleportingNextFrame || IsSynchronizing) + { + ReliableFragmentedSequenced = true; + } + else + { + ReliableFragmentedSequenced = false; + } + BytePacker.WriteValueBitPacked(m_Writer, m_Bitset); // We use network ticks as opposed to absolute time as the authoritative // side updates on every new tick. @@ -620,6 +649,8 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { if (UseHalfFloatPrecision) { + NetworkDeltaPosition.SynchronizeBase = SynchronizeBaseHalfFloat; + // Apply which axis should be updated for both write/read (teleporting, synchronizing, or just updating) NetworkDeltaPosition.HalfVector3.AxisToSynchronize[0] = HasPositionX; NetworkDeltaPosition.HalfVector3.AxisToSynchronize[1] = HasPositionY; @@ -1583,11 +1614,34 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw networkState.HasPositionZ = true; isPositionDirty = true; } + + if (networkState.HasPositionX || networkState.HasPositionY || networkState.HasPositionZ) + { + if (SyncPositionX && !networkState.HasPositionX) + { + networkState.HasPositionX = true; + networkState.PositionX = position.x; + } + if (SyncPositionY && !networkState.HasPositionY) + { + networkState.HasPositionY = true; + networkState.PositionY = position.y; + } + if (SyncPositionZ && !networkState.HasPositionZ) + { + networkState.HasPositionZ = true; + networkState.PositionZ = position.z; + } + } } else if (SynchronizePosition) { // If we are teleporting then we can skip the delta threshold check isPositionDirty = networkState.IsTeleportingNextFrame; + if (m_HalfFloatTargetTickOwnership > NetworkManager.ServerTime.Tick) + { + isPositionDirty = true; + } // For NetworkDeltaPosition, if any axial value is dirty then we always send a full update if (!isPositionDirty) @@ -1626,8 +1680,17 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw m_HalfPositionState.HalfVector3.AxisToSynchronize = math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ); m_HalfPositionState.UpdateFrom(ref position, networkState.NetworkTick); } - - networkState.NetworkDeltaPosition = m_HalfPositionState; + if (m_HalfFloatTargetTickOwnership > NetworkManager.ServerTime.Tick && !networkState.IsTeleportingNextFrame) + { + networkState.NetworkDeltaPosition = m_HalfPositionState; + networkState.SynchronizeBaseHalfFloat = true; + } + else + { + networkState.NetworkDeltaPosition = m_HalfPositionState; + networkState.SynchronizeBaseHalfFloat = false; + } + } else // If synchronizing is set, then use the current full position value on the server side { @@ -2236,12 +2299,20 @@ private void ApplyUpdatedState(NetworkTransformState newState) // Only if using half float precision and our position had changed last update then if (UseHalfFloatPrecision && m_LocalAuthoritativeNetworkState.HasPositionChange) { - // assure our local NetworkDeltaPosition state is updated - m_HalfPositionState.HalfVector3.Axis = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.HalfVector3.Axis; + if (m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat) + { + m_HalfPositionState = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition; + } + else + { + // assure our local NetworkDeltaPosition state is updated + m_HalfPositionState.HalfVector3.Axis = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.HalfVector3.Axis; + m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.CurrentBasePosition = m_HalfPositionState.CurrentBasePosition; + m_LocalAuthoritativeNetworkState.CurrentPosition = m_TargetPosition; + } // and update our target position m_TargetPosition = m_HalfPositionState.ToVector3(newState.NetworkTick); - m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.CurrentBasePosition = m_HalfPositionState.CurrentBasePosition; - m_LocalAuthoritativeNetworkState.CurrentPosition = m_TargetPosition; + } if (!Interpolate) @@ -2558,7 +2629,8 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) // If we were the previous owner or the newly assigned owner then reinitialize if (current == NetworkManager.LocalClientId || previous == NetworkManager.LocalClientId) { - Initialize(); + Initialize(true); + } base.OnOwnershipChanged(previous, current); } @@ -2585,11 +2657,12 @@ protected virtual void OnInitialize(ref NetworkVariable r } + private int m_HalfFloatTargetTickOwnership; /// /// Initializes NetworkTransform when spawned and ownership changes. /// - protected void Initialize() + protected void Initialize(bool isOwnershipChange = false) { if (!IsSpawned) { @@ -2612,8 +2685,16 @@ protected void Initialize() NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick; NetworkManager.NetworkTickSystem.Tick += NetworkTickSystem_Tick; - // Teleport to current position - SetStateInternal(currentPosition, currentRotation, transform.localScale, true); + m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false; + if (UseHalfFloatPrecision && isOwnershipChange && !IsServerAuthoritative() && Interpolate) + { + m_HalfFloatTargetTickOwnership = NetworkManager.ServerTime.Tick + 3; + } + else + { + // Teleport to current position + SetStateInternal(currentPosition, currentRotation, transform.localScale, true); + } } else { @@ -2622,7 +2703,7 @@ protected void Initialize() NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick; ResetInterpolatedStateToCurrentAuthoritativeState(); - + m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false; m_CurrentPosition = currentPosition; m_TargetPosition = currentPosition; m_CurrentScale = transform.localScale; @@ -2773,19 +2854,9 @@ private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool SetStateInternal(pos, rot, scale, shouldTeleport); } - /// - /// - /// If you override this method, be sure that: - /// - Non-authority always invokes this base class method. - /// - protected virtual void Update() - { - // If not spawned or this instance has authority, exit early - if (!IsSpawned || CanCommitToTransform) - { - return; - } + private void UpdateInterpolation(bool isBlend = false ) + { // Non-Authority if (Interpolate) { @@ -2819,6 +2890,23 @@ protected virtual void Update() m_ScaleInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); } } + } + + /// + /// + /// If you override this method, be sure that: + /// - Non-authority always invokes this base class method. + /// + protected virtual void Update() + { + // If not spawned or this instance has authority, exit early + if (!IsSpawned || CanCommitToTransform) + { + return; + } + + // Non-Authority + UpdateInterpolation(); // Apply the current authoritative state ApplyAuthoritativeState(); @@ -2877,26 +2965,31 @@ private void TransformStateUpdate(ulong senderId, FastBufferReader messagePayloa return; } - // Forward owner authoritative messages before doing anything else - if (IsServer && !OnIsServerAuthoritative()) - { - ForwardStateUpdateMessage(messagePayload); - } // Store the previous/old state m_OldState = m_LocalAuthoritativeNetworkState; + var currentPosition = messagePayload.Position; // Deserialize the message messagePayload.ReadNetworkSerializableInPlace(ref m_LocalAuthoritativeNetworkState); + messagePayload.Seek(currentPosition); + var sendMode = m_LocalAuthoritativeNetworkState.ReliableFragmentedSequenced ? NetworkDelivery.ReliableFragmentedSequenced : NetworkDelivery.UnreliableSequenced; + // Forward owner authoritative messages before doing anything else + if (IsServer && !OnIsServerAuthoritative()) + { + ForwardStateUpdateMessage(messagePayload, sendMode); + } + // Apply the message OnNetworkStateChanged(m_OldState, m_LocalAuthoritativeNetworkState); + } /// /// Forwards owner authoritative state updates when received by the server /// /// the owner state message payload - private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload) + private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload, NetworkDelivery networkDelivery) { var serverAuthoritative = OnIsServerAuthoritative(); var currentPosition = messagePayload.Position; @@ -2914,7 +3007,7 @@ private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload) { continue; } - NetworkManager.CustomMessagingManager.SendNamedMessage(m_MessageName, clientId, writer); + NetworkManager.CustomMessagingManager.SendNamedMessage(m_MessageName, clientId, writer, networkDelivery); } } messagePayload.Seek(currentPosition); @@ -2943,6 +3036,8 @@ private void UpdateTransformState() var writer = new FastBufferWriter(128, Allocator.Temp); + var sendMode = m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame | m_LocalAuthoritativeNetworkState.IsSynchronizing ? NetworkDelivery.ReliableFragmentedSequenced : NetworkDelivery.UnreliableSequenced; + using (writer) { writer.WriteNetworkSerializable(m_LocalAuthoritativeNetworkState); @@ -2957,13 +3052,13 @@ private void UpdateTransformState() { continue; } - customMessageManager.SendNamedMessage(m_MessageName, clientId, writer); + customMessageManager.SendNamedMessage(m_MessageName, clientId, writer, sendMode); } } else { // Clients (owner authoritative) send messages to the server-host - customMessageManager.SendNamedMessage(m_MessageName, NetworkManager.ServerClientId, writer); + customMessageManager.SendNamedMessage(m_MessageName, NetworkManager.ServerClientId, writer, sendMode); } } } From db0124a8a37c09ad0a7ef5911079a3f837244cc2 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 15 Sep 2023 18:06:08 -0500 Subject: [PATCH 06/12] style white space fixes --- .../Components/NetworkTransform.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index bb51cb6064..ea24ecb63b 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -1690,7 +1690,7 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw networkState.NetworkDeltaPosition = m_HalfPositionState; networkState.SynchronizeBaseHalfFloat = false; } - + } else // If synchronizing is set, then use the current full position value on the server side { @@ -2855,7 +2855,7 @@ private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool } - private void UpdateInterpolation(bool isBlend = false ) + private void UpdateInterpolation(bool isBlend = false) { // Non-Authority if (Interpolate) @@ -3036,7 +3036,7 @@ private void UpdateTransformState() var writer = new FastBufferWriter(128, Allocator.Temp); - var sendMode = m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame | m_LocalAuthoritativeNetworkState.IsSynchronizing ? NetworkDelivery.ReliableFragmentedSequenced : NetworkDelivery.UnreliableSequenced; + var sendMode = m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame | m_LocalAuthoritativeNetworkState.IsSynchronizing ? NetworkDelivery.ReliableFragmentedSequenced : NetworkDelivery.UnreliableSequenced; using (writer) { From 1e3a874f81de4ccc8707e1eeba08b926039d9168 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 16 Sep 2023 14:11:04 -0500 Subject: [PATCH 07/12] test Fixing issue with changes that require a NetworkManager to be present. --- .../Runtime/NetworkTransform/NetworkTransformStateTests.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs index 2669c233a9..18dc864a1f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformStateTests.cs @@ -230,6 +230,11 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu var gameObject = new GameObject($"Test-{nameof(NetworkTransformStateTests)}.{nameof(TestSyncAxes)}"); var networkObject = gameObject.AddComponent(); var networkTransform = gameObject.AddComponent(); + + var manager = new GameObject($"Test-{nameof(NetworkManager)}.{nameof(TestSyncAxes)}"); + var networkManager = manager.AddComponent(); + networkObject.NetworkManagerOwner = networkManager; + networkTransform.enabled = false; // do not tick `FixedUpdate()` or `Update()` var initialPosition = Vector3.zero; @@ -269,6 +274,7 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu if (syncPosX || syncPosY || syncPosZ || syncRotX || syncRotY || syncRotZ || syncScaX || syncScaY || syncScaZ) { + Assert.IsTrue(networkTransform.NetworkManager != null, "NetworkManager is NULL!"); Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform)); } } @@ -714,6 +720,7 @@ public void TestSyncAxes([Values] SynchronizationType synchronizationType, [Valu } Object.DestroyImmediate(gameObject); + Object.DestroyImmediate(manager); } From d63a7fb55e608df9385fde00f79c0a3c34149da4 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 18 Sep 2023 11:59:25 -0500 Subject: [PATCH 08/12] fix Fixing new issues (due to changes) with position not synchronizing to the target position when half float precision was enabled. --- .../Components/NetworkTransform.cs | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index ea24ecb63b..45784b7f9a 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -1985,7 +1985,7 @@ private void ApplyAuthoritativeState() { if (networkState.HasPositionChange && SynchronizePosition) { - adjustedPosition = networkState.CurrentPosition; + adjustedPosition = m_TargetPosition; } if (networkState.HasScaleChange && SynchronizeScale) @@ -2308,11 +2308,12 @@ private void ApplyUpdatedState(NetworkTransformState newState) // assure our local NetworkDeltaPosition state is updated m_HalfPositionState.HalfVector3.Axis = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.HalfVector3.Axis; m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.CurrentBasePosition = m_HalfPositionState.CurrentBasePosition; - m_LocalAuthoritativeNetworkState.CurrentPosition = m_TargetPosition; + + // This is to assure when you get the position of the state it is the correct position + m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.ToVector3(0); } - // and update our target position + // Update our target position m_TargetPosition = m_HalfPositionState.ToVector3(newState.NetworkTick); - } if (!Interpolate) @@ -2446,7 +2447,9 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf ApplyUpdatedState(newState); // Provide notifications when the state has been updated - OnNetworkTransformStateUpdated(ref oldState, ref newState); + // 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) + OnNetworkTransformStateUpdated(ref oldState, ref m_LocalAuthoritativeNetworkState); } /// @@ -2958,7 +2961,8 @@ public bool IsServerAuthoritative() /// serialzied private void TransformStateUpdate(ulong senderId, FastBufferReader messagePayload) { - if (!OnIsServerAuthoritative() && IsServer && OwnerClientId == NetworkManager.ServerClientId) + var ownerAuthoritativeServerSide = !OnIsServerAuthoritative() && IsServer; + if (ownerAuthoritativeServerSide && OwnerClientId == NetworkManager.ServerClientId) { // Ownership must have changed, ignore any additional pending messages that might have // come from a previous owner client. @@ -2968,16 +2972,22 @@ private void TransformStateUpdate(ulong senderId, FastBufferReader messagePayloa // Store the previous/old state m_OldState = m_LocalAuthoritativeNetworkState; + // Save the current payload stream position var currentPosition = messagePayload.Position; - // Deserialize the message + + // Deserialize the message (and determine network delivery) messagePayload.ReadNetworkSerializableInPlace(ref m_LocalAuthoritativeNetworkState); + // Rewind back prior to serialization messagePayload.Seek(currentPosition); - var sendMode = m_LocalAuthoritativeNetworkState.ReliableFragmentedSequenced ? NetworkDelivery.ReliableFragmentedSequenced : NetworkDelivery.UnreliableSequenced; + + // Get the network delivery method used to send this state update + var networkDelivery = m_LocalAuthoritativeNetworkState.ReliableFragmentedSequenced ? NetworkDelivery.ReliableFragmentedSequenced : NetworkDelivery.UnreliableSequenced; + // Forward owner authoritative messages before doing anything else - if (IsServer && !OnIsServerAuthoritative()) + if (ownerAuthoritativeServerSide) { - ForwardStateUpdateMessage(messagePayload, sendMode); + ForwardStateUpdateMessage(messagePayload, networkDelivery); } // Apply the message @@ -3036,7 +3046,8 @@ private void UpdateTransformState() var writer = new FastBufferWriter(128, Allocator.Temp); - var sendMode = m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame | m_LocalAuthoritativeNetworkState.IsSynchronizing ? NetworkDelivery.ReliableFragmentedSequenced : NetworkDelivery.UnreliableSequenced; + // Determine what network delivery method to use + var networkDelivery = m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame | m_LocalAuthoritativeNetworkState.IsSynchronizing ? NetworkDelivery.ReliableFragmentedSequenced : NetworkDelivery.UnreliableSequenced; using (writer) { @@ -3052,13 +3063,13 @@ private void UpdateTransformState() { continue; } - customMessageManager.SendNamedMessage(m_MessageName, clientId, writer, sendMode); + customMessageManager.SendNamedMessage(m_MessageName, clientId, writer, networkDelivery); } } else { // Clients (owner authoritative) send messages to the server-host - customMessageManager.SendNamedMessage(m_MessageName, NetworkManager.ServerClientId, writer, sendMode); + customMessageManager.SendNamedMessage(m_MessageName, NetworkManager.ServerClientId, writer, networkDelivery); } } } From 1a2cad3cfbe507239617112e6e31b73e4b362c70 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 18 Sep 2023 12:01:03 -0500 Subject: [PATCH 09/12] test minor adjustments to account for minor precision delta when quaternion compression is enabled in the NetworkTransformTests.ParentedNetworkTransformTest --- .../Runtime/NetworkTransform/NetworkTransformTests.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index a29c9630e6..189b48f713 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -470,7 +470,7 @@ private void AllChildrenLocalTransformValuesMatch(bool useSubChild) var childLocalRotation = childInstance.transform.localRotation.eulerAngles; var childLocalScale = childInstance.transform.localScale; // Adjust approximation based on precision - if (m_Precision == Precision.Half) + if (m_Precision == Precision.Half || m_RotationCompression == RotationCompression.QuaternionCompress) { m_CurrentHalfPrecision = k_HalfPrecisionPosScale; } @@ -486,7 +486,7 @@ private void AllChildrenLocalTransformValuesMatch(bool useSubChild) } // Adjust approximation based on precision - if (m_Precision == Precision.Half) + if (m_Precision == Precision.Half || m_RotationCompression == RotationCompression.QuaternionCompress) { m_CurrentHalfPrecision = k_HalfPrecisionRot; } @@ -523,6 +523,7 @@ public void ParentedNetworkTransformTest([Values] Precision precision, [Values] { // Set the precision being used for threshold adjustments m_Precision = precision; + m_RotationCompression = rotationCompression; // Get the NetworkManager that will have authority in order to spawn with the correct authority var isServerAuthority = m_Authority == Authority.ServerAuthority; @@ -577,6 +578,9 @@ public void ParentedNetworkTransformTest([Values] Precision precision, [Values] // Allow one tick for authority to update these changes TimeTravelToNextTick(); + 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!"); @@ -689,6 +693,7 @@ protected override void OnNewClientCreated(NetworkManager networkManager) } private Precision m_Precision = Precision.Full; + private RotationCompression m_RotationCompression = RotationCompression.None; private float m_CurrentHalfPrecision = 0.0f; private const float k_HalfPrecisionPosScale = 0.041f; private const float k_HalfPrecisionRot = 0.725f; From b15b2710763f2c092163eacbc0273bf4cfbf3ca7 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 18 Sep 2023 13:23:54 -0500 Subject: [PATCH 10/12] update Resolves the api validation issue. --- .../Components/NetworkTransform.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 45784b7f9a..9908c6a241 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -2632,8 +2632,7 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) // If we were the previous owner or the newly assigned owner then reinitialize if (current == NetworkManager.LocalClientId || previous == NetworkManager.LocalClientId) { - Initialize(true); - + InternalInitialization(true); } base.OnOwnershipChanged(previous, current); } @@ -2663,9 +2662,10 @@ protected virtual void OnInitialize(ref NetworkVariable r private int m_HalfFloatTargetTickOwnership; /// - /// Initializes NetworkTransform when spawned and ownership changes. + /// The internal initialzation method to allow for internal API adjustments /// - protected void Initialize(bool isOwnershipChange = false) + /// + private void InternalInitialization(bool isOwnershipChange = false) { if (!IsSpawned) { @@ -2701,7 +2701,6 @@ protected void Initialize(bool isOwnershipChange = false) } else { - // Assure we no longer subscribe to the tick event NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick; @@ -2724,6 +2723,14 @@ protected void Initialize(bool isOwnershipChange = false) } } + /// + /// Initializes NetworkTransform when spawned and ownership changes. + /// + protected void Initialize() + { + InternalInitialization(); + } + /// /// /// When a parent changes, non-authoritative instances should: From 20674eadda93ad516663d758c43e13ea4c14fdc5 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 18 Sep 2023 14:57:07 -0500 Subject: [PATCH 11/12] update Updating rotation and scale delta checks when sending unreliable messages. --- .../Components/NetworkDeltaPosition.cs | 5 + .../Components/NetworkTransform.cs | 91 +++++++++++++++++-- 2 files changed, 87 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs b/com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs index df21e62d8a..ec5c176874 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkDeltaPosition.cs @@ -25,6 +25,8 @@ public struct NetworkDeltaPosition : INetworkSerializable internal bool SynchronizeBase; + internal bool CollapsedDeltaIntoBase; + /// /// The serialization implementation of /// @@ -128,6 +130,7 @@ public Vector3 GetDeltaPosition() [MethodImpl(MethodImplOptions.AggressiveInlining)] public void UpdateFrom(ref Vector3 vector3, int networkTick) { + CollapsedDeltaIntoBase = false; NetworkTick = networkTick; DeltaPosition = (vector3 + PrecisionLossDelta) - CurrentBasePosition; for (int i = 0; i < HalfVector3.Length; i++) @@ -142,6 +145,7 @@ public void UpdateFrom(ref Vector3 vector3, int networkTick) CurrentBasePosition[i] += HalfDeltaConvertedBack[i]; HalfDeltaConvertedBack[i] = 0.0f; DeltaPosition[i] = 0.0f; + CollapsedDeltaIntoBase = true; } } } @@ -171,6 +175,7 @@ public NetworkDeltaPosition(Vector3 vector3, int networkTick, bool3 axisToSynchr HalfDeltaConvertedBack = Vector3.zero; HalfVector3 = new HalfVector3(vector3, axisToSynchronize); SynchronizeBase = false; + CollapsedDeltaIntoBase = false; UpdateFrom(ref vector3, networkTick); } diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 9908c6a241..4d485f3045 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -55,6 +55,8 @@ public class NetworkTransform : NetworkBehaviour /// internal static bool TrackByStateId; + internal bool UseUnreliableDeltas = true; + /// /// Data structure used to synchronize the /// @@ -80,6 +82,7 @@ public struct NetworkTransformState : INetworkSerializable private const int k_IsParented = 0x00020000; // When parented and synchronizing, we need to have both lossy and local scale due to varying spawn order private const int k_SynchBaseHalfFloat = 0x00040000; private const int k_ReliableFragmentedSequenced = 0x00080000; + private const int k_UseUnreliableDeltas = 0x00100000; private const int k_TrackStateId = 0x10000000; // (Internal Debugging) When set each state update will contain a state identifier // Stores persistent and state relative flags @@ -458,6 +461,15 @@ internal bool ReliableFragmentedSequenced } } + internal bool UseUnreliableDeltas + { + get => GetFlag(k_UseUnreliableDeltas); + set + { + SetFlag(value, k_UseUnreliableDeltas); + } + } + internal bool TrackByStateId { get => GetFlag(k_TrackStateId); @@ -483,7 +495,7 @@ private void SetFlag(bool set, int flag) internal void ClearBitSetForNextTick() { // Clear everything but flags that should persist between state updates until changed by authority - m_Bitset &= k_InLocalSpaceBit | k_Interpolate | k_UseHalfFloats | k_QuaternionSync | k_QuaternionCompress | k_PositionSlerp; + m_Bitset &= k_InLocalSpaceBit | k_Interpolate | k_UseHalfFloats | k_QuaternionSync | k_QuaternionCompress | k_PositionSlerp | k_UseUnreliableDeltas; IsDirty = false; } @@ -614,13 +626,21 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade { if (isWriting) { - if (IsTeleportingNextFrame || IsSynchronizing) - { - ReliableFragmentedSequenced = true; - } - else + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!!!!! TODO: Revisit this flag, we might not need it + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + if (UseUnreliableDeltas) { - ReliableFragmentedSequenced = false; + // If teleporting, synchronizing, or using half float precision and we collapsed a delta into the base position + if (IsTeleportingNextFrame || IsSynchronizing || (UseHalfFloatPrecision && NetworkDeltaPosition.CollapsedDeltaIntoBase)) + { + // Send the message reliably + ReliableFragmentedSequenced = true; + } + else + { + ReliableFragmentedSequenced = false; + } } BytePacker.WriteValueBitPacked(m_Writer, m_Bitset); @@ -1526,6 +1546,7 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat networkState.QuaternionSync = UseQuaternionSynchronization; networkState.UseHalfFloatPrecision = UseHalfFloatPrecision; networkState.QuaternionCompression = UseQuaternionCompression; + networkState.UseUnreliableDeltas = UseUnreliableDeltas; m_HalfPositionState = new NetworkDeltaPosition(Vector3.zero, 0, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); return ApplyTransformToNetworkStateWithInfo(ref networkState, ref transformToUse); @@ -1592,6 +1613,16 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw networkState.IsTeleportingNextFrame = true; } + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!!!!! TODO: Revisit this flag, we might not need it + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + if (UseUnreliableDeltas != networkState.UseUnreliableDeltas) + { + networkState.UseUnreliableDeltas = UseUnreliableDeltas; + isDirty = true; + networkState.IsTeleportingNextFrame = true; + } + if (!UseHalfFloatPrecision) { if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame)) @@ -1615,7 +1646,8 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw isPositionDirty = true; } - if (networkState.HasPositionX || networkState.HasPositionY || networkState.HasPositionZ) + // When sending deltas unreliably, we send all axis marked for synchronization if one axis changes + if (UseUnreliableDeltas && (networkState.HasPositionX || networkState.HasPositionY || networkState.HasPositionZ)) { if (SyncPositionX && !networkState.HasPositionX) { @@ -1688,7 +1720,8 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw else { networkState.NetworkDeltaPosition = m_HalfPositionState; - networkState.SynchronizeBaseHalfFloat = false; + + networkState.SynchronizeBaseHalfFloat = UseUnreliableDeltas ? m_HalfPositionState.CollapsedDeltaIntoBase : false; } } @@ -1762,6 +1795,26 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw networkState.HasRotAngleZ = true; isRotationDirty = true; } + + // When sending deltas unreliably, we send all axis marked for synchronization if one axis changes + if (UseUnreliableDeltas && (networkState.HasRotAngleX || networkState.HasRotAngleY || networkState.HasRotAngleZ)) + { + if (SyncRotAngleX && !networkState.HasRotAngleX) + { + networkState.HasRotAngleX = true; + networkState.RotAngleX = rotAngles.x; + } + if (SyncRotAngleY && !networkState.HasRotAngleY) + { + networkState.HasRotAngleY = true; + networkState.RotAngleY = rotAngles.y; + } + if (SyncRotAngleZ && !networkState.HasRotAngleZ) + { + networkState.HasRotAngleZ = true; + networkState.RotAngleZ = rotAngles.z; + } + } } else if (SynchronizeRotation) { @@ -1850,6 +1903,26 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw networkState.HasScaleZ = true; isScaleDirty = true; } + + // When sending deltas unreliably, we send all axis marked for synchronization if one axis changes + if (UseUnreliableDeltas && (networkState.HasScaleX || networkState.HasScaleY || networkState.HasScaleZ)) + { + if (SyncScaleX && !networkState.HasScaleX) + { + networkState.HasScaleX = true; + networkState.ScaleX = scale.x; + } + if (SyncScaleY && !networkState.HasScaleY) + { + networkState.HasScaleY = true; + networkState.ScaleY = scale.y; + } + if (SyncScaleZ && !networkState.HasScaleZ) + { + networkState.HasScaleZ = true; + networkState.ScaleZ = scale.z; + } + } } else if (SynchronizeScale) { From 8cfea24329ecd9bad42e1db4b1d5ca30f1945d9e Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 18 Sep 2023 18:48:29 -0500 Subject: [PATCH 12/12] update Was doing some profiling and found a few low hanging fruit areas that helps improve performance. --- .../Components/NetworkTransform.cs | 147 ++++++++++++++---- 1 file changed, 115 insertions(+), 32 deletions(-) diff --git a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs index 4d485f3045..e3502472d6 100644 --- a/com.unity.netcode.gameobjects/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Components/NetworkTransform.cs @@ -1134,7 +1134,7 @@ private bool SynchronizeScale /// Internally used by to keep track of the instance assigned to this /// this derived class instance. /// - protected NetworkManager m_CachedNetworkManager; // Note: we no longer use this and are only keeping it until we decide to deprecate it + protected NetworkManager m_CachedNetworkManager; /// /// Helper method that returns the space relative position of the transform. @@ -1541,6 +1541,7 @@ internal NetworkTransformState ApplyLocalNetworkState(Transform transform) /// internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse) { + m_CachedNetworkManager = NetworkManager; // Apply the interpolate and PostionDeltaCompression flags, otherwise we get false positives whether something changed or not. networkState.UseInterpolation = Interpolate; networkState.QuaternionSync = UseQuaternionSynchronization; @@ -1670,7 +1671,7 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw { // If we are teleporting then we can skip the delta threshold check isPositionDirty = networkState.IsTeleportingNextFrame; - if (m_HalfFloatTargetTickOwnership > NetworkManager.ServerTime.Tick) + if (m_HalfFloatTargetTickOwnership > m_CachedNetworkManager.ServerTime.Tick) { isPositionDirty = true; } @@ -1712,7 +1713,7 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw m_HalfPositionState.HalfVector3.AxisToSynchronize = math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ); m_HalfPositionState.UpdateFrom(ref position, networkState.NetworkTick); } - if (m_HalfFloatTargetTickOwnership > NetworkManager.ServerTime.Tick && !networkState.IsTeleportingNextFrame) + if (m_HalfFloatTargetTickOwnership > m_CachedNetworkManager.ServerTime.Tick && !networkState.IsTeleportingNextFrame) { networkState.NetworkDeltaPosition = m_HalfPositionState; networkState.SynchronizeBaseHalfFloat = true; @@ -1964,7 +1965,7 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw // NetworkManager if (enabled) { - networkState.NetworkTick = NetworkManager.ServerTime.Tick; + networkState.NetworkTick = m_CachedNetworkManager.ServerTime.Tick; } } @@ -2203,6 +2204,7 @@ private void ApplyTeleportingState(NetworkTransformState newState) // offset or not. This is specific to owner authoritative mode on the owner side only if (isSynchronization) { + // Need to use NetworkManager vs m_CachedNetworkManager here since we are yet to be spawned if (ShouldSynchronizeHalfFloat(NetworkManager.LocalClientId)) { m_HalfPositionState.HalfVector3.Axis = newState.NetworkDeltaPosition.HalfVector3.Axis; @@ -2514,7 +2516,7 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf } // Get the time when this new state was sent - newState.SentTime = new NetworkTime(NetworkManager.NetworkConfig.TickRate, newState.NetworkTick).Time; + newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkConfig.TickRate, newState.NetworkTick).Time; // Apply the new state ApplyUpdatedState(newState); @@ -2638,14 +2640,6 @@ private void NetworkTickSystem_Tick() 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; - } - } } /// @@ -2654,22 +2648,28 @@ public override void OnNetworkSpawn() /////////////////////////////////////////////////////////////// // NOTE: Legacy and no longer used (candidates for deprecation) m_CachedIsServer = IsServer; - m_CachedNetworkManager = NetworkManager; /////////////////////////////////////////////////////////////// + // Started using this again to avoid the getter processing cost of NetworkBehaviour.NetworkManager + m_CachedNetworkManager = NetworkManager; + // Register a custom named message specifically for this instance m_MessageName = $"NTU_{NetworkObjectId}_{NetworkBehaviourId}"; - NetworkManager.CustomMessagingManager.RegisterNamedMessageHandler(m_MessageName, TransformStateUpdate); + m_CachedNetworkManager.CustomMessagingManager.RegisterNamedMessageHandler(m_MessageName, TransformStateUpdate); Initialize(); } /// public override void OnNetworkDespawn() { + // During destroy, use NetworkBehaviour.NetworkManager as opposed to m_CachedNetworkManager if (!NetworkManager.ShutdownInProgress && NetworkManager.CustomMessagingManager != null) { NetworkManager.CustomMessagingManager.UnregisterNamedMessageHandler(m_MessageName); } + + DeregisterForTickUpdate(this); + CanCommitToTransform = false; if (NetworkManager != null && NetworkManager.NetworkTickSystem != null) { @@ -2680,6 +2680,7 @@ public override void OnNetworkDespawn() /// public override void OnDestroy() { + // During destroy, use NetworkBehaviour.NetworkManager as opposed to m_CachedNetworkManager if (NetworkManager != null && NetworkManager.NetworkTickSystem != null) { NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick; @@ -2703,7 +2704,7 @@ public override void OnGainedOwnership() protected override void OnOwnershipChanged(ulong previous, ulong current) { // If we were the previous owner or the newly assigned owner then reinitialize - if (current == NetworkManager.LocalClientId || previous == NetworkManager.LocalClientId) + if (current == m_CachedNetworkManager.LocalClientId || previous == m_CachedNetworkManager.LocalClientId) { InternalInitialization(true); } @@ -2753,18 +2754,17 @@ private void InternalInitialization(bool isOwnershipChange = false) { if (UseHalfFloatPrecision) { - m_HalfPositionState = new NetworkDeltaPosition(currentPosition, NetworkManager.NetworkTickSystem.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); + m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.NetworkTickSystem.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); } m_CurrentPosition = currentPosition; m_TargetPosition = currentPosition; - // Authority only updates once per network tick - NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick; - NetworkManager.NetworkTickSystem.Tick += NetworkTickSystem_Tick; + + RegisterForTickUpdate(this); m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false; if (UseHalfFloatPrecision && isOwnershipChange && !IsServerAuthoritative() && Interpolate) { - m_HalfFloatTargetTickOwnership = NetworkManager.ServerTime.Tick + 3; + m_HalfFloatTargetTickOwnership = m_CachedNetworkManager.ServerTime.Tick + 3; } else { @@ -2774,8 +2774,8 @@ private void InternalInitialization(bool isOwnershipChange = false) } else { - // Assure we no longer subscribe to the tick event - NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick; + // Remove this instance from the tick update + DeregisterForTickUpdate(this); ResetInterpolatedStateToCurrentAuthoritativeState(); m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat = false; @@ -2823,6 +2823,7 @@ public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObj 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); @@ -2943,8 +2944,8 @@ private void UpdateInterpolation(bool isBlend = false) // Non-Authority if (Interpolate) { - var serverTime = NetworkManager.ServerTime; - var cachedDeltaTime = NetworkManager.RealTimeProvider.DeltaTime; + var serverTime = m_CachedNetworkManager.ServerTime; + var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; var cachedServerTime = serverTime.Time; // With owner authoritative mode, non-authority clients can lag behind @@ -3089,15 +3090,15 @@ private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload, N { writer.WriteBytesSafe(messagePayload.GetUnsafePtr(), messageSize, currentPosition); - var clientCount = NetworkManager.ConnectionManager.ConnectedClientsList.Count; + var clientCount = m_CachedNetworkManager.ConnectionManager.ConnectedClientsList.Count; for (int i = 0; i < clientCount; i++) { - var clientId = NetworkManager.ConnectionManager.ConnectedClientsList[i].ClientId; + var clientId = m_CachedNetworkManager.ConnectionManager.ConnectedClientsList[i].ClientId; if (NetworkManager.ServerClientId == clientId || (!serverAuthoritative && clientId == OwnerClientId)) { continue; } - NetworkManager.CustomMessagingManager.SendNamedMessage(m_MessageName, clientId, writer, networkDelivery); + m_CachedNetworkManager.CustomMessagingManager.SendNamedMessage(m_MessageName, clientId, writer, networkDelivery); } } messagePayload.Seek(currentPosition); @@ -3108,7 +3109,7 @@ private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload, N /// private void UpdateTransformState() { - if (NetworkManager.ShutdownInProgress) + if (m_CachedNetworkManager.ShutdownInProgress) { return; } @@ -3122,7 +3123,7 @@ private void UpdateTransformState() { Debug.LogError($"Owner authoritative {nameof(NetworkTransform)} can only be updated by the owner!"); } - var customMessageManager = NetworkManager.CustomMessagingManager; + var customMessageManager = m_CachedNetworkManager.CustomMessagingManager; var writer = new FastBufferWriter(128, Allocator.Temp); @@ -3135,10 +3136,10 @@ private void UpdateTransformState() // Server-host always sends updates to all clients (but itself) if (IsServer) { - var clientCount = NetworkManager.ConnectionManager.ConnectedClientsList.Count; + var clientCount = m_CachedNetworkManager.ConnectionManager.ConnectedClientsList.Count; for (int i = 0; i < clientCount; i++) { - var clientId = NetworkManager.ConnectionManager.ConnectedClientsList[i].ClientId; + var clientId = m_CachedNetworkManager.ConnectionManager.ConnectedClientsList[i].ClientId; if (NetworkManager.ServerClientId == clientId) { continue; @@ -3153,6 +3154,88 @@ private void UpdateTransformState() } } } + + + private static Dictionary s_NetworkTickRegistration = new Dictionary(); + + private static void RemoveTickUpdate(NetworkManager networkManager) + { + s_NetworkTickRegistration.Remove(networkManager); + } + + /// + /// Having the tick update once and cycling through registered instances to update is evidently less processor + /// intensive than having each instance subscribe and update individually. + /// + private class NetworkTransformTickRegistration + { + private Action m_NetworkTickUpdate; + private NetworkManager m_NetworkManager; + public HashSet NetworkTransforms = new HashSet(); + private void OnNetworkManagerStopped(bool value) + { + m_NetworkManager.NetworkTickSystem.Tick -= m_NetworkTickUpdate; + m_NetworkTickUpdate = null; + NetworkTransforms.Clear(); + RemoveTickUpdate(m_NetworkManager); + } + + /// + /// Invoked once per network tick, this will update any registered + /// authority instances. + /// + private void TickUpdate() + { + foreach (var networkTransform in NetworkTransforms) + { + networkTransform.NetworkTickSystem_Tick(); + } + } + + public NetworkTransformTickRegistration(NetworkManager networkManager) + { + m_NetworkManager = networkManager; + m_NetworkTickUpdate = new Action(TickUpdate); + networkManager.NetworkTickSystem.Tick += m_NetworkTickUpdate; + if (networkManager.IsServer) + { + networkManager.OnServerStopped += OnNetworkManagerStopped; + } + else + { + networkManager.OnClientStopped += OnNetworkManagerStopped; + } + } + } + + /// + /// Will register the NetworkTransform instance for the single tick update entry point. + /// If a NetworkTransformTickRegistration has not yet been registered for the NetworkManager + /// instance, then create an entry. + /// + /// + private static void RegisterForTickUpdate(NetworkTransform networkTransform) + { + if (!s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager)) + { + s_NetworkTickRegistration.Add(networkTransform.NetworkManager, new NetworkTransformTickRegistration(networkTransform.NetworkManager)); + } + s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Add(networkTransform); + } + + + /// + /// If a NetworkTransformTickRegistration exists for the NetworkManager instanc, then this will + /// remove the NetworkTransform instance from the single tick update entry point. + /// + /// + private static void DeregisterForTickUpdate(NetworkTransform networkTransform) + { + if (s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager)) + { + s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Remove(networkTransform); + } + } } internal interface INetworkTransformLogStateEntry