From bc2e9f242bf876660b6fb29796ea86cde0950474 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 19 Mar 2025 12:19:10 -0500 Subject: [PATCH 01/50] wip Getting closer to what I am hoping to achieve. --- .../BufferedLinearInterpolator.cs | 142 ++++++++++-------- .../Runtime/Components/NetworkTransform.cs | 34 +++-- .../Runtime/Core/NetworkManager.cs | 22 +++ 3 files changed, 127 insertions(+), 71 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 0aabd0226e..95c690934d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -121,14 +121,15 @@ internal struct CurrentState public float TimeToTargetValue; public float DeltaTime; public float LerpT; - + public bool TargetReached; public T CurrentValue; public T PreviousValue; + public T PredictValue; private float m_AverageDeltaTime; public float AverageDeltaTime => m_AverageDeltaTime; - public float FinalTimeToTarget => TimeToTargetValue - DeltaTime; + public float FinalTimeToTarget => Mathf.Max(0.0f, TimeToTargetValue - DeltaTime); public void AddDeltaTime(float deltaTime) { @@ -151,13 +152,13 @@ public void ResetDelta() DeltaTime = 0.0f; } - public bool TargetTimeAproximatelyReached() + public bool TargetTimeAproximatelyReached(float adjustForNext = 1.0f) { if (!Target.HasValue) { return false; } - return m_AverageDeltaTime >= FinalTimeToTarget; + return (m_AverageDeltaTime * adjustForNext) >= FinalTimeToTarget; } public void Reset(T currentValue) @@ -165,6 +166,7 @@ public void Reset(T currentValue) Target = null; CurrentValue = currentValue; PreviousValue = currentValue; + TargetReached = false; // When reset, we consider ourselves to have already arrived at the target (even if no target is set) LerpT = 0.0f; EndTime = 0.0; @@ -273,69 +275,81 @@ private void InternalReset(T targetValue, double serverTime, bool isAngularValue /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime) { - if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime - && (InterpolateState.TargetTimeAproximatelyReached() || IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item)))) + BufferedItem? previousItem = null; + var startTime = 0.0; + var alreadyHasBufferItem = false; + var noStateSet = !InterpolateState.Target.HasValue; + var potentialItemNeedsProcessing = false; + var currentTargetTimeReached = false; + + while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) { - BufferedItem? previousItem = null; - var startTime = 0.0; - var alreadyHasBufferItem = false; - while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) + // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing + // to consume. + if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) { - // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing - // to consume. - if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) + break; + } + + if (!noStateSet) + { + potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent >= InterpolateState.Target.Value.TimeSent; + currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing ? 1.5f : 1.0f); + if (!potentialItemNeedsProcessing && !InterpolateState.TargetReached) { - break; + InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); } + } - // If we haven't set a target or the potential item's time sent is less that the current target's time sent - // then pull the BufferedItem from the queue. The second portion of this accounts for scenarios where there - // was bad latency and the buffer has more than one item in the queue that is less than the renderTime. Under - // this scenario, we just want to continue pulling items from the queue until the last item pulled from the - // queue is greater than the redner time or greater than the currently targeted item. - if (!InterpolateState.Target.HasValue || - ((potentialItem.TimeSent <= renderTime) && InterpolateState.Target.Value.TimeSent <= potentialItem.TimeSent)) + // If we haven't set a target or the potential item's time sent is less that the current target's time sent + // then pull the BufferedItem from the queue. The second portion of this accounts for scenarios where there + // was bad latency and the buffer has more than one item in the queue that is less than the renderTime. Under + // this scenario, we just want to continue pulling items from the queue until the last item pulled from the + // queue is greater than the redner time or greater than the currently targeted item. + if (noStateSet || ((currentTargetTimeReached || InterpolateState.TargetReached) && potentialItemNeedsProcessing)) + { + if (m_BufferQueue.TryDequeue(out BufferedItem target)) { - if (m_BufferQueue.TryDequeue(out BufferedItem target)) + if (!InterpolateState.Target.HasValue) { - if (!InterpolateState.Target.HasValue) + InterpolateState.Target = target; + + alreadyHasBufferItem = true; + InterpolateState.PredictValue = InterpolateState.CurrentValue; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.TimeToTargetValue = minDeltaTime; + startTime = InterpolateState.Target.Value.TimeSent; + InterpolateState.TargetReached = false; + } + else + { + if (!alreadyHasBufferItem) { - InterpolateState.Target = target; - alreadyHasBufferItem = true; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; - InterpolateState.TimeToTargetValue = minDeltaTime; startTime = InterpolateState.Target.Value.TimeSent; + //InterpolateState.PreviousValue = InterpolateState.CurrentValue; + //InterpolateState.PredictValue = InterpolateState.CurrentValue; + InterpolateState.LerpT = 0.0f; + InterpolateState.TargetReached = false; } - else - { - if (!alreadyHasBufferItem) - { - alreadyHasBufferItem = true; - startTime = InterpolateState.Target.Value.TimeSent; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; - InterpolateState.LerpT = 0.0f; - } - // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated - // for each item as opposed to losing the resolution of the values. - InterpolateState.TimeToTargetValue = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); - - InterpolateState.Target = target; - } - InterpolateState.ResetDelta(); + // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated + // for each item as opposed to losing the resolution of the values. + InterpolateState.TimeToTargetValue = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); + InterpolateState.Target = target; } + InterpolateState.ResetDelta(); } - else - { - break; - } + } + else + { + break; + } - if (!InterpolateState.Target.HasValue) - { - break; - } - previousItem = potentialItem; + if (!InterpolateState.Target.HasValue) + { + break; } + previousItem = potentialItem; } } @@ -355,18 +369,24 @@ internal T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, { TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime); // Only interpolate when there is a start and end point and we have not already reached the end value - if (InterpolateState.Target.HasValue) + if (InterpolateState.Target.HasValue && !InterpolateState.TargetReached) { InterpolateState.AddDeltaTime(deltaTime); - // Smooth dampen our current time - var current = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime); - // Smooth dampen a predicted time based on our average delta time - var predict = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + (InterpolateState.AverageDeltaTime * 2)); - // Lerp between the current and predicted. - // Note: Since smooth dampening cannot over shoot, both current and predict will eventually become the same or will be very close to the same. - // Upon stopping motion, the final resing value should be a very close aproximation of the authority side. - InterpolateState.CurrentValue = Interpolate(current, predict, deltaTime); + { + + // Smooth dampen our current time + InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime); + InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, Mathf.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime)); + // Smooth dampen a predicted time based on our minimum delta time + //var predict = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, Mathf.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime)); + + // Lerp between the current and predicted. + // Note: Since smooth dampening cannot over shoot, both current and predict will eventually become the same or will be very close to the same. + // Upon stopping motion, the final resing value should be a very close aproximation of the authority side. + InterpolateState.CurrentValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime); + + } } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index d0951c6cfb..ef2eca77fc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3836,11 +3836,12 @@ private void UpdateInterpolation() var cachedServerTime = m_CachedNetworkManager.ServerTime.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; + //var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; + var cachedDeltaTime = Time.deltaTime; #else var cachedDeltaTime = Time.deltaTime; #endif - var tickLatency = m_CachedNetworkManager.NetworkTimeSystem.TickLatency; + var tickLatency = m_CachedNetworkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset; // If using an owner authoritative motion model if (!IsServerAuthoritative()) @@ -3866,8 +3867,11 @@ private void UpdateInterpolation() // frame update. Since smooth dampening is most useful for Rigidbody motion, the physics update // frequency is roughly 60hz (59.x?) which 2x that value as frequency is typically close to 32-33ms. // Look within the Interpolator.Update for smooth dampening to better understand the above. - var maxDeltaTime = (1.666667f * m_CachedNetworkManager.ServerTime.FixedDeltaTime); + + //var frameRateRatio = Application.targetFrameRate > 0 ? Application.targetFrameRate * cachedDeltaTime : 100 * cachedDeltaTime; + //var maxDeltaTime = (Math.Max(60, frameRateRatio * m_CachedNetworkManager.ServerTime.FixedDeltaTime)); + var maxDeltaTime = tickLatency * minDeltaTime; // Now only update the interpolators for the portions of the transform being synchronized if (SynchronizePosition) { @@ -3942,6 +3946,16 @@ public virtual void OnUpdate() } #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D + + internal void OnFixedUpdateInterpolate() + { + // Update interpolation when enabled + if (Interpolate) + { + UpdateInterpolation(); + } + } + /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . @@ -3958,10 +3972,10 @@ public virtual void OnFixedUpdate() // Update interpolation when enabled - if (Interpolate) - { - UpdateInterpolation(); - } + //if (Interpolate) + //{ + // UpdateInterpolation(); + //} // Apply the current authoritative state ApplyAuthoritativeState(); @@ -4118,12 +4132,12 @@ private void UpdateTransformState() #region NETWORK TICK REGISTRATOIN AND HANDLING private static Dictionary s_NetworkTickRegistration = new Dictionary(); - + internal static int InterpolationBufferTickOffset = 2; internal static float GetTickLatency(NetworkManager networkManager) { if (networkManager.IsListening) { - return (float)(networkManager.NetworkTimeSystem.TickLatency + networkManager.LocalTime.TickOffset); + return (float)(networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset + networkManager.LocalTime.TickOffset); } return 0; } @@ -4145,7 +4159,7 @@ internal static float GetTickLatencyInSeconds(NetworkManager networkManager) { if (networkManager.IsListening) { - return (float)networkManager.LocalTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency).Time; + return (float)networkManager.LocalTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset).Time; } return 0f; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index d564f0f8f3..c3e4dc839a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -361,7 +361,29 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) case NetworkUpdateStage.PreUpdate: { NetworkTimeSystem.UpdateTime(); +#if COM_UNITY_MODULES_PHYSICS + foreach (var networkObjectEntry in NetworkTransformFixedUpdate) + { + // if not active or not spawned then skip + if (!networkObjectEntry.Value.gameObject.activeInHierarchy || !networkObjectEntry.Value.IsSpawned) + { + continue; + } + + foreach (var networkTransformEntry in networkObjectEntry.Value.NetworkTransforms) + { + // only update if enabled + if (networkTransformEntry.enabled) + { + networkTransformEntry.OnFixedUpdateInterpolate(); + } + } + } +#endif + AnticipationSystem.Update(); + + } break; case NetworkUpdateStage.PreLateUpdate: From fe93343c20caba84c0bffa8b4a2c55ef60fb88e5 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 20 Mar 2025 20:55:25 -0500 Subject: [PATCH 02/50] update Adding more properties for users to adjust as well as an additional interpolation type as there are various use cases where one model doesn't fit all genres. --- .../Editor/NetworkTransformEditor.cs | 38 ++-- .../BufferedLinearInterpolator.cs | 131 +++++++++--- .../Runtime/Components/NetworkTransform.cs | 191 ++++++++++++++---- .../Runtime/Core/NetworkManager.cs | 57 ++++-- 4 files changed, 313 insertions(+), 104 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index b04ad3f724..53d09b782f 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -31,6 +31,9 @@ public class NetworkTransformEditor : NetcodeEditorBase private SerializedProperty m_PositionInterpolationTypeProperty; private SerializedProperty m_RotationInterpolationTypeProperty; private SerializedProperty m_ScaleInterpolationTypeProperty; + private SerializedProperty m_PositionLerpSmoothing; + private SerializedProperty m_RotationLerpSmoothing; + private SerializedProperty m_ScaleLerpSmoothing; private SerializedProperty m_PositionMaximumInterpolationTimeProperty; private SerializedProperty m_RotationMaximumInterpolationTimeProperty; @@ -77,6 +80,11 @@ public override void OnEnable() m_ScaleInterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleInterpolationType)); m_ScaleMaximumInterpolationTimeProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleMaxInterpolationTime)); + m_PositionLerpSmoothing = serializedObject.FindProperty(nameof(NetworkTransform.PositionLerpSmoothing)); + m_RotationLerpSmoothing = serializedObject.FindProperty(nameof(NetworkTransform.RotationLerpSmoothing)); + m_ScaleLerpSmoothing = serializedObject.FindProperty(nameof(NetworkTransform.ScaleLerpSmoothing)); + + m_UseQuaternionSynchronization = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionSynchronization)); m_UseQuaternionCompression = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionCompression)); @@ -198,36 +206,42 @@ private void DisplayNetworkTransformProperties() if (networkTransform.SynchronizePosition) { DrawPropertyField(m_PositionInterpolationTypeProperty); - // Only display when using Lerp. - if (networkTransform.PositionInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + + BeginIndent(); + if (networkTransform.PositionInterpolationType != NetworkTransform.InterpolationTypes.SmoothDampening) { - BeginIndent(); DrawPropertyField(m_SlerpPosition); + } + DrawPropertyField(m_PositionLerpSmoothing); + if (networkTransform.PositionLerpSmoothing) + { DrawPropertyField(m_PositionMaximumInterpolationTimeProperty); - EndIndent(); } + EndIndent(); } if (networkTransform.SynchronizeRotation) { DrawPropertyField(m_RotationInterpolationTypeProperty); - // Only display when using Lerp. - if (networkTransform.RotationInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + + BeginIndent(); + DrawPropertyField(m_RotationLerpSmoothing); + if (networkTransform.RotationLerpSmoothing) { - BeginIndent(); DrawPropertyField(m_RotationMaximumInterpolationTimeProperty); - EndIndent(); } + EndIndent(); } if (networkTransform.SynchronizeScale) { DrawPropertyField(m_ScaleInterpolationTypeProperty); - // Only display when using Lerp. - if (networkTransform.ScaleInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + + BeginIndent(); + DrawPropertyField(m_ScaleLerpSmoothing); + if (networkTransform.ScaleLerpSmoothing) { - BeginIndent(); DrawPropertyField(m_ScaleMaximumInterpolationTimeProperty); - EndIndent(); } + EndIndent(); } EndIndent(); EditorGUILayout.Space(); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 95c690934d..6a161f672d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -120,11 +120,16 @@ internal struct CurrentState public double EndTime; public float TimeToTargetValue; public float DeltaTime; + public float DeltaTimePredict; + public float MaxDeltaTime; public float LerpT; + public float LerpTPredict; public bool TargetReached; + public bool PredictingNext; public T CurrentValue; public T PreviousValue; public T PredictValue; + public T PredictTarget; private float m_AverageDeltaTime; @@ -143,7 +148,25 @@ public void AddDeltaTime(float deltaTime) m_AverageDeltaTime *= 0.5f; } DeltaTime = Math.Min(DeltaTime + m_AverageDeltaTime, TimeToTargetValue); + DeltaTimePredict = Math.Min(DeltaTime + DeltaTimePredict, TimeToTargetValue + MaxDeltaTime); LerpT = TimeToTargetValue == 0.0f ? 1.0f : DeltaTime / TimeToTargetValue; + if (PredictingNext) + { + LerpTPredict = TimeToTargetValue == 0.0f ? 1.0f : DeltaTimePredict / TimeToTargetValue; + } + else + { + LerpTPredict = LerpT; + } + } + + public void SetTimeToTarget(float timeToTarget) + { + DeltaTimePredict = 0.0f; + LerpTPredict = 0.0f; + LerpT = 0.0f; + DeltaTime = 0.0f; + TimeToTargetValue = timeToTarget; } public void ResetDelta() @@ -166,11 +189,17 @@ public void Reset(T currentValue) Target = null; CurrentValue = currentValue; PreviousValue = currentValue; + PredictValue = currentValue; + PredictTarget = currentValue; TargetReached = false; - // When reset, we consider ourselves to have already arrived at the target (even if no target is set) + PredictingNext = false; LerpT = 0.0f; + LerpTPredict = 0.0f; EndTime = 0.0; StartTime = 0.0; + TimeToTargetValue = 0.0f; + DeltaTime = 0.0f; + DeltaTimePredict = 0.0f; ResetDelta(); } } @@ -222,6 +251,8 @@ public void Reset(T currentValue) /// private protected bool m_IsAngularValue; + private bool m_WasPredictedLerp; + /// /// Resets interpolator to the defaults. /// @@ -273,7 +304,7 @@ private void InternalReset(T targetValue, double serverTime, bool isAngularValue /// render time: the time in "ticks ago" relative to the current tick latency /// minimum time delta (defaults to tick frequency) /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer - private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime) + private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime, bool isPredictedLerp) { BufferedItem? previousItem = null; var startTime = 0.0; @@ -294,7 +325,7 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m if (!noStateSet) { potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent >= InterpolateState.Target.Value.TimeSent; - currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing ? 1.5f : 1.0f); + currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing ? 1.15f : 0.85f); if (!potentialItemNeedsProcessing && !InterpolateState.TargetReached) { InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); @@ -317,24 +348,33 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m alreadyHasBufferItem = true; InterpolateState.PredictValue = InterpolateState.CurrentValue; InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.SetTimeToTarget(minDeltaTime); InterpolateState.TimeToTargetValue = minDeltaTime; startTime = InterpolateState.Target.Value.TimeSent; InterpolateState.TargetReached = false; + InterpolateState.PredictingNext = false; + InterpolateState.MaxDeltaTime = maxDeltaTime; } else { if (!alreadyHasBufferItem) { alreadyHasBufferItem = true; - startTime = InterpolateState.Target.Value.TimeSent; - //InterpolateState.PreviousValue = InterpolateState.CurrentValue; - //InterpolateState.PredictValue = InterpolateState.CurrentValue; - InterpolateState.LerpT = 0.0f; InterpolateState.TargetReached = false; + InterpolateState.PredictingNext = false; + startTime = InterpolateState.Target.Value.TimeSent; + InterpolateState.PredictTarget = target.Item; + InterpolateState.MaxDeltaTime = maxDeltaTime; + if (m_BufferQueue.TryPeek(out BufferedItem lookAheadItem)) + { + InterpolateState.PredictTarget = Interpolate(target.Item, lookAheadItem.Item, InterpolateState.AverageDeltaTime); + InterpolateState.PredictingNext = true; + } } // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated // for each item as opposed to losing the resolution of the values. - InterpolateState.TimeToTargetValue = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); + var timeToTarget = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); + InterpolateState.SetTimeToTarget(timeToTarget); InterpolateState.Target = target; } InterpolateState.ResetDelta(); @@ -353,6 +393,14 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m } } + internal void ResetCurrentState() + { + if (InterpolateState.Target.HasValue) + { + InterpolateState.Reset(InterpolateState.CurrentValue); + } + } + /// /// Interpolation Update to use when smooth dampening is enabled on a . /// @@ -364,28 +412,45 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m /// The tick latency in relative local time. /// The minimum time delta between the current and target value. /// The maximum time delta between the current and target value. + /// Determines whether to use smooth dampening or extrapolation. + /// Determines if lerp smoothing is enabled for this instance. /// The newly interpolated value of type 'T' - internal T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, float maxDeltaTime) + internal T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, float maxDeltaTime, bool isLerpAndExtrapolate, bool lerpSmoothing) { - TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime); - // Only interpolate when there is a start and end point and we have not already reached the end value - if (InterpolateState.Target.HasValue && !InterpolateState.TargetReached) + TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime, isLerpAndExtrapolate); + // Only begin interpolation when there is a start and end point + if (InterpolateState.Target.HasValue) { - InterpolateState.AddDeltaTime(deltaTime); - + // As long as the target hasn't been reached, interpolate or smooth dampen. + if (!InterpolateState.TargetReached) { - - // Smooth dampen our current time - InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime); - InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, Mathf.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime)); - // Smooth dampen a predicted time based on our minimum delta time - //var predict = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, Mathf.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime)); - - // Lerp between the current and predicted. - // Note: Since smooth dampening cannot over shoot, both current and predict will eventually become the same or will be very close to the same. - // Upon stopping motion, the final resing value should be a very close aproximation of the authority side. - InterpolateState.CurrentValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime); + InterpolateState.AddDeltaTime(deltaTime); + + // SmoothDampen or LerpExtrapolateBlend + if (!isLerpAndExtrapolate) + { + InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime); + var predictedTime = InterpolateState.PredictingNext ? InterpolateState.DeltaTime : Mathf.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); + InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.PredictTarget, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, predictedTime); + } + else + { + InterpolateState.PreviousValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); + InterpolateState.PredictValue = InterpolateUnclamped(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, InterpolateState.LerpTPredict); + } + // Lerp between the PreviousValue and PredictedValue (extrapolated) using this frame's delta time + var targetValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime); + if (lerpSmoothing) + { + // If lerp smoothing is enabled, then smooth current value towards the target value + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / MaximumInterpolationTime); + } + else + { + // Otherwise, just assign the target value. + InterpolateState.CurrentValue = targetValue; + } } } m_NbItemsReceivedThisFrame = 0; @@ -467,8 +532,9 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) /// time since last call /// our current time /// current server time + /// Determines if lerp smoothing is enabled for this instance. /// The newly interpolated value of type 'T' - public T Update(float deltaTime, double renderTime, double serverTime) + public T Update(float deltaTime, double renderTime, double serverTime, bool lerpSmoothing = true) { TryConsumeFromBuffer(renderTime, serverTime); // Only interpolate when there is a start and end point and we have not already reached the end value @@ -495,9 +561,16 @@ public T Update(float deltaTime, double renderTime, double serverTime) } var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, t); - // Assure our MaximumInterpolationTime is valid and that the second lerp time ranges between deltaTime and 1.0f. - var secondLerpTime = Mathf.Clamp(deltaTime / Mathf.Max(deltaTime, MaximumInterpolationTime), deltaTime, 1.0f); - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, secondLerpTime); + if (lerpSmoothing) + { + // Assure our MaximumInterpolationTime is valid and that the second lerp time ranges between deltaTime and 1.0f. + var secondLerpTime = Mathf.Clamp(deltaTime / MaximumInterpolationTime, deltaTime, 1.0f); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, secondLerpTime); + } + else + { + InterpolateState.CurrentValue = target; + } } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index ef2eca77fc..30de861837 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -938,24 +938,65 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade /// /// The different interpolation types used with to help smooth interpolation results. + /// Interpolation types can be changed during runtime. /// public enum InterpolationTypes { /// - /// Uses lerping and yields a linear progression between two values. + /// Lerp (original NGO lerping model)
+ /// Uses a 1 to 2 phase interpolation approach where:
+ /// - The fist phase lerps from the previous state update value to the next state update value.
+ /// - The second phase (optional) performs a lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of delta time divided by the respective max interpolation time. ///
/// + /// Lowest computation cost of the three options.
/// For more information:
/// -
+ /// -
/// -
+ /// -
/// -
+ /// -
///
Lerp, /// - /// Uses a smooth dampening approach for interpolating between two data points and adjusts based on rate of change. + /// Lerp, Extrapolate, and Blend + /// Uses a 3 to 4 phase lerp towards the target, extrapolate towards the target, blend the two results, and (optionally) smooth the final value.
+ /// - The first phase lerps towards the current tick state update being processed.
+ /// - The second phase lerps unclamped (extrapolates) towards the current tick state update and will extrapolate this value up to a calculated maximum delta time. + /// The maximum delta time is the tick latency, calculated from an estimated RTT each time the network time is updated, plus the . The sum is multiplied by the tick frequency (one over tick rate).
+ /// - The third phase lerps between the results of the first and second phases by the current delta time. + /// - The fourth phase (optional) performs a lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. ///
/// - /// Unlike , there are no additional values needed to be adjusted for this interpolation type. + /// Note: This is slightly more computationally expensive than the approach.
+ /// It is recommended to turn lerp smoothing off or adjust the interpolation time to a lower value if you want a more precise end result.
+ /// For more information:
+ /// -
+ /// -
+ /// -
+ /// -
+ /// -
+ /// -
+ ///
+ LerpExtrapolateBlend, + /// + /// Uses a 3 to 4 phase smooth dampen towards the target, smooth dampen towards the next target, blend the two results, and (optionally) smooth the final value.
+ /// - The first phase smooth dampens towards the current tick state update being processed.
+ /// - The second phase smooth dampens towards the next tick state's target. If there is no next tick state update, then the target predicted value is the current state target that smooth dampens 1 frame (average delta time) ahead.
+ /// - The third phase lerps between the results of the first and second phases by the current delta time. + /// - The fourth phase (optional) performs a lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. + ///
+ /// + /// Note: Smooth dampening is computationally more expensive than the and approaches.
+ /// It is recommended to turn lerp smoothing off or adjust the interpolation time to a lower value if you want a more precise end result. + /// For more information:
+ /// -
+ /// -
+ /// -
+ /// -
+ /// -
+ /// -
///
SmoothDampening } @@ -971,6 +1012,7 @@ public enum InterpolationTypes /// [Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes PositionInterpolationType; + private InterpolationTypes m_PreviousPositionInterpolationType; /// /// The rotation interpolation type to use for the instance. @@ -983,6 +1025,7 @@ public enum InterpolationTypes /// [Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes RotationInterpolationType; + private InterpolationTypes m_PreviousRotationInterpolationType; /// /// The scale interpolation type to use for the instance. @@ -995,6 +1038,16 @@ public enum InterpolationTypes /// [Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes ScaleInterpolationType; + private InterpolationTypes m_PreviousScaleInterpolationType; + + /// + /// Controls position interpolaiton smoothing. + /// + /// + /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . + /// + public bool PositionLerpSmoothing; + private bool m_PreviousPositionLerpSmoothing; /// /// The position interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. @@ -1010,6 +1063,15 @@ public enum InterpolationTypes [Range(0.01f, 1.0f)] public float PositionMaxInterpolationTime = 0.1f; + /// + /// Controls rotation interpolaiton smoothing. + /// + /// + /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . + /// + public bool RotationLerpSmoothing; + private bool m_PreviousRotationLerpSmoothing; + /// /// The rotation interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
@@ -1024,6 +1086,15 @@ public enum InterpolationTypes [Range(0.01f, 1.0f)] public float RotationMaxInterpolationTime = 0.1f; + /// + /// Controls scale interpolaiton smoothing. + /// + /// + /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . + /// + public bool ScaleLerpSmoothing; + private bool m_PreviousScaleLerpSmoothing; + /// /// The scale interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
@@ -3388,7 +3459,6 @@ private void InternalInitialization(bool isOwnershipChange = false) // Determine if this is the first NetworkTransform in the associated NetworkObject's list m_IsFirstNetworkTransform = NetworkObject.NetworkTransforms[0] == this; - if (m_CachedNetworkManager && m_CachedNetworkManager.DistributedAuthorityMode) { AuthorityMode = AuthorityModes.Owner; @@ -3456,6 +3526,14 @@ private void InternalInitialization(bool isOwnershipChange = false) } else { + // Always set these during initialization for non-authority so we can detect a change in interpolator types + m_PreviousPositionInterpolationType = PositionInterpolationType; + m_PreviousRotationInterpolationType = RotationInterpolationType; + m_PreviousScaleInterpolationType = ScaleInterpolationType; + m_PreviousPositionLerpSmoothing = PositionLerpSmoothing; + m_PreviousRotationLerpSmoothing = RotationLerpSmoothing; + m_PreviousScaleLerpSmoothing = ScaleLerpSmoothing; + // Non-authority needs to be added to updates for interpolation and applying state purposes m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, true); // Remove this instance from the tick update @@ -3829,6 +3907,8 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca #region UPDATES AND AUTHORITY CHECKS private NetworkTransformTickRegistration m_NetworkTransformTickRegistration; + + // Non-Authority private void UpdateInterpolation() { @@ -3836,8 +3916,7 @@ private void UpdateInterpolation() var cachedServerTime = m_CachedNetworkManager.ServerTime.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - //var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; - var cachedDeltaTime = Time.deltaTime; + var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; #else var cachedDeltaTime = Time.deltaTime; #endif @@ -3858,61 +3937,95 @@ private void UpdateInterpolation() } } } - + tickLatency += InterpolationBufferTickOffset; var tickLatencyAsTime = m_CachedNetworkManager.LocalTime.TimeTicksAgo(tickLatency).Time; + // Smooth dampening specific: - // We clamp between tick rate and bit beyond the tick rate but not 2x tick rate (we predict 2x out) + // We clamp between the tick rate frequency and the tick latency x tick rate frequency var minDeltaTime = m_CachedNetworkManager.LocalTime.FixedDeltaTime; - // The 1.666667f value is a "magic" number tht lies between the FixedDeltaTime and 2 * the averaged - // frame update. Since smooth dampening is most useful for Rigidbody motion, the physics update - // frequency is roughly 60hz (59.x?) which 2x that value as frequency is typically close to 32-33ms. - // Look within the Interpolator.Update for smooth dampening to better understand the above. - - - //var frameRateRatio = Application.targetFrameRate > 0 ? Application.targetFrameRate * cachedDeltaTime : 100 * cachedDeltaTime; - //var maxDeltaTime = (Math.Max(60, frameRateRatio * m_CachedNetworkManager.ServerTime.FixedDeltaTime)); + + // Maximum delta time is the maximum time we will lerp between values. If the time exceeds this due to extreme + // latency then the value's interpolation rate will be accelerated to reach the goal and continue interpolating + // the next state updates. var maxDeltaTime = tickLatency * minDeltaTime; + // Now only update the interpolators for the portions of the transform being synchronized if (SynchronizePosition) { - if (PositionInterpolationType == InterpolationTypes.Lerp) + if (PositionLerpSmoothing) { m_PositionInterpolator.MaximumInterpolationTime = PositionMaxInterpolationTime; - m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + + if (m_PreviousPositionInterpolationType != PositionInterpolationType || m_PreviousPositionLerpSmoothing != PositionLerpSmoothing) + { + m_PreviousPositionInterpolationType = PositionInterpolationType; + m_PreviousPositionLerpSmoothing = PositionLerpSmoothing; + m_PositionInterpolator.ResetCurrentState(); + } + + if (PositionInterpolationType == InterpolationTypes.Lerp) + { + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime, PositionLerpSmoothing); } else { - m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, + PositionInterpolationType == InterpolationTypes.LerpExtrapolateBlend, PositionLerpSmoothing); } } if (SynchronizeRotation) { - if (RotationInterpolationType == InterpolationTypes.Lerp) + if (RotationLerpSmoothing) { m_RotationInterpolator.MaximumInterpolationTime = RotationMaxInterpolationTime; + } + + if (m_PreviousRotationInterpolationType != RotationInterpolationType || m_PreviousRotationLerpSmoothing != RotationLerpSmoothing) + { + m_PreviousRotationInterpolationType = RotationInterpolationType; + m_PreviousRotationLerpSmoothing = RotationLerpSmoothing; + m_RotationInterpolator.ResetCurrentState(); + } + + if (RotationInterpolationType == InterpolationTypes.Lerp) + { // When using half precision Lerp towards the target rotation. // When using full precision Slerp towards the target rotation. /// m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; - m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime, RotationLerpSmoothing); } else { - m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); + m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, + RotationInterpolationType == InterpolationTypes.LerpExtrapolateBlend, RotationLerpSmoothing); } } if (SynchronizeScale) { - if (ScaleInterpolationType == InterpolationTypes.Lerp) + if (ScaleLerpSmoothing) { m_ScaleInterpolator.MaximumInterpolationTime = ScaleMaxInterpolationTime; - m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + + if (m_PreviousScaleInterpolationType != ScaleInterpolationType || m_PreviousScaleLerpSmoothing != ScaleLerpSmoothing) + { + m_PreviousScaleInterpolationType = ScaleInterpolationType; + m_PreviousScaleLerpSmoothing = ScaleLerpSmoothing; + m_ScaleInterpolator.ResetCurrentState(); + } + + if (ScaleInterpolationType == InterpolationTypes.Lerp) + { + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime, ScaleLerpSmoothing); } else { - m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, + ScaleInterpolationType == InterpolationTypes.LerpExtrapolateBlend, ScaleLerpSmoothing); } } } @@ -3946,16 +4059,6 @@ public virtual void OnUpdate() } #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - - internal void OnFixedUpdateInterpolate() - { - // Update interpolation when enabled - if (Interpolate) - { - UpdateInterpolation(); - } - } - /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . @@ -3970,12 +4073,11 @@ public virtual void OnFixedUpdate() m_NetworkRigidbodyInternal.WakeIfSleeping(); - // Update interpolation when enabled - //if (Interpolate) - //{ - // UpdateInterpolation(); - //} + if (Interpolate) + { + UpdateInterpolation(); + } // Apply the current authoritative state ApplyAuthoritativeState(); @@ -4132,7 +4234,14 @@ private void UpdateTransformState() #region NETWORK TICK REGISTRATOIN AND HANDLING private static Dictionary s_NetworkTickRegistration = new Dictionary(); - internal static int InterpolationBufferTickOffset = 2; + /// + /// Adjusts the over-all tick offset (i.e. how many ticks ago) and how wide of a maximum delta time will be used for the various . + /// + /// + /// Note: You can adjust this value during runtime. Increasing this value will set non-authority instances that much further behind the authority instance but + /// will increase the number of state updates to be processed. This can be useful under higher latency conditions. + /// + public static int InterpolationBufferTickOffset = 0; internal static float GetTickLatency(NetworkManager networkManager) { if (networkManager.IsListening) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index c3e4dc839a..3520826eab 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -9,6 +9,7 @@ #endif using UnityEngine.SceneManagement; using Debug = UnityEngine.Debug; +using Unity.Netcode.Components; namespace Unity.Netcode { @@ -312,6 +313,40 @@ private void UpdateTopology() } } + internal void CheckNetworkTransformsForDuplicateRegistrations() + { + var uniqueEntries = new Dictionary(); + var uniqueNetworkTransforms = new Dictionary>(); + foreach (var networkObject in NetworkTransformFixedUpdate.Values) + { + if (!uniqueEntries.ContainsKey(networkObject.NetworkObjectId)) + { + uniqueEntries.Add(networkObject.NetworkObjectId, networkObject); + uniqueNetworkTransforms.Add(networkObject.NetworkObjectId, new Dictionary()); + foreach (var networkTransform in networkObject.NetworkTransforms) + { + if (!uniqueNetworkTransforms.ContainsKey(networkTransform.NetworkBehaviourId)) + { + if (!uniqueNetworkTransforms[networkObject.NetworkObjectId].ContainsKey(networkTransform.NetworkBehaviourId)) + { + uniqueNetworkTransforms[networkObject.NetworkObjectId].Add(networkTransform.NetworkBehaviourId, networkTransform); + } + else + { + Debug.LogError($"{networkObject.name}-{networkObject.NetworkObjectId} {nameof(NetworkTransform)}-{networkTransform.NetworkBehaviourId} has a duplicate {nameof(NetworkTransform)} entry!"); + } + } + } + } + else + { + Debug.LogError($"{networkObject.name}-{networkObject.NetworkObjectId} has a duplicate update entry!"); + } + } + Debug.Log($"There are {NetworkTransformFixedUpdate.Count} NetworkTransforms registered for the fixed update."); + Debug.Log($"There are {NetworkTransformUpdate.Count} NetworkTransforms registered for the normal update."); + } + public void NetworkUpdate(NetworkUpdateStage updateStage) { switch (updateStage) @@ -361,29 +396,7 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) case NetworkUpdateStage.PreUpdate: { NetworkTimeSystem.UpdateTime(); -#if COM_UNITY_MODULES_PHYSICS - foreach (var networkObjectEntry in NetworkTransformFixedUpdate) - { - // if not active or not spawned then skip - if (!networkObjectEntry.Value.gameObject.activeInHierarchy || !networkObjectEntry.Value.IsSpawned) - { - continue; - } - - foreach (var networkTransformEntry in networkObjectEntry.Value.NetworkTransforms) - { - // only update if enabled - if (networkTransformEntry.enabled) - { - networkTransformEntry.OnFixedUpdateInterpolate(); - } - } - } -#endif - AnticipationSystem.Update(); - - } break; case NetworkUpdateStage.PreLateUpdate: From 65dac85298b8ef71dfbdcd9ae1413c4b999773d2 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 20 Mar 2025 22:05:10 -0500 Subject: [PATCH 03/50] fix Just wrapping some code that I will be removing anyway before marking this PR ready for review. Adding some line breaks in new XML API documentation. --- .../Runtime/Components/NetworkTransform.cs | 4 ++-- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 30de861837..cbad521f0f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -965,7 +965,7 @@ public enum InterpolationTypes /// - The first phase lerps towards the current tick state update being processed.
/// - The second phase lerps unclamped (extrapolates) towards the current tick state update and will extrapolate this value up to a calculated maximum delta time. /// The maximum delta time is the tick latency, calculated from an estimated RTT each time the network time is updated, plus the . The sum is multiplied by the tick frequency (one over tick rate).
- /// - The third phase lerps between the results of the first and second phases by the current delta time. + /// - The third phase lerps between the results of the first and second phases by the current delta time.
/// - The fourth phase (optional) performs a lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. ///
/// @@ -984,7 +984,7 @@ public enum InterpolationTypes /// Uses a 3 to 4 phase smooth dampen towards the target, smooth dampen towards the next target, blend the two results, and (optionally) smooth the final value.
/// - The first phase smooth dampens towards the current tick state update being processed.
/// - The second phase smooth dampens towards the next tick state's target. If there is no next tick state update, then the target predicted value is the current state target that smooth dampens 1 frame (average delta time) ahead.
- /// - The third phase lerps between the results of the first and second phases by the current delta time. + /// - The third phase lerps between the results of the first and second phases by the current delta time.
/// - The fourth phase (optional) performs a lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. ///
/// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 3520826eab..afb91e7f0e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -312,7 +312,7 @@ private void UpdateTopology() IsDistributedAuthority = DistributedAuthorityMode = transportTopology == NetworkTopologyTypes.DistributedAuthority; } } - +#if COM_UNITY_MODULES_PHYSICS internal void CheckNetworkTransformsForDuplicateRegistrations() { var uniqueEntries = new Dictionary(); @@ -346,6 +346,7 @@ internal void CheckNetworkTransformsForDuplicateRegistrations() Debug.Log($"There are {NetworkTransformFixedUpdate.Count} NetworkTransforms registered for the fixed update."); Debug.Log($"There are {NetworkTransformUpdate.Count} NetworkTransforms registered for the normal update."); } +#endif public void NetworkUpdate(NetworkUpdateStage updateStage) { From f0fb0fdaa753f64aeca26eecb73db712e6c16092 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 01:48:04 -0500 Subject: [PATCH 04/50] update Updating PositionMaxInterpolationTime, RotationMaxInterpolationTime, and ScaleMaxInterpolationTime to be set when `NetworkTransform.SetMaxInterpolationBound` is invoked. --- .../Runtime/Components/NetworkTransform.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index cbad521f0f..aa387a614f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3149,6 +3149,9 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf /// Maximum time boundary that can be used in a frame when interpolating between two values public void SetMaxInterpolationBound(float maxInterpolationBound) { + PositionMaxInterpolationTime = maxInterpolationBound; + RotationMaxInterpolationTime = maxInterpolationBound; + ScaleMaxInterpolationTime = maxInterpolationBound; m_RotationInterpolator.MaxInterpolationBound = maxInterpolationBound; m_PositionInterpolator.MaxInterpolationBound = maxInterpolationBound; m_ScaleInterpolator.MaxInterpolationBound = maxInterpolationBound; @@ -3937,10 +3940,14 @@ private void UpdateInterpolation() } } } + // Optional user defined tick offset to be used to push the "render time" (the time that will be used to determine if a state update is available) + // back in order to provide more room for the interpolator to interpolate towards when latency conditions are impacting the frequency that state + // updates are received. tickLatency += InterpolationBufferTickOffset; + var tickLatencyAsTime = m_CachedNetworkManager.LocalTime.TimeTicksAgo(tickLatency).Time; - // Smooth dampening specific: + // Smooth dampening and extrapolation specific: // We clamp between the tick rate frequency and the tick latency x tick rate frequency var minDeltaTime = m_CachedNetworkManager.LocalTime.FixedDeltaTime; @@ -3957,6 +3964,7 @@ private void UpdateInterpolation() m_PositionInterpolator.MaximumInterpolationTime = PositionMaxInterpolationTime; } + // If either of these two position interpolation related values have changed, then reset the current state being interpolated. if (m_PreviousPositionInterpolationType != PositionInterpolationType || m_PreviousPositionLerpSmoothing != PositionLerpSmoothing) { m_PreviousPositionInterpolationType = PositionInterpolationType; @@ -3982,6 +3990,7 @@ private void UpdateInterpolation() m_RotationInterpolator.MaximumInterpolationTime = RotationMaxInterpolationTime; } + // If either of these two rotation interpolation related values have changed, then reset the current state being interpolated. if (m_PreviousRotationInterpolationType != RotationInterpolationType || m_PreviousRotationLerpSmoothing != RotationLerpSmoothing) { m_PreviousRotationInterpolationType = RotationInterpolationType; @@ -4011,6 +4020,7 @@ private void UpdateInterpolation() m_ScaleInterpolator.MaximumInterpolationTime = ScaleMaxInterpolationTime; } + // If either of these two rotation interpolation related values have changed, then reset the current state being interpolated. if (m_PreviousScaleInterpolationType != ScaleInterpolationType || m_PreviousScaleLerpSmoothing != ScaleLerpSmoothing) { m_PreviousScaleInterpolationType = ScaleInterpolationType; From 81e551c85ced684b2056fa79f2d6cba9fc494356 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 01:51:00 -0500 Subject: [PATCH 05/50] style updating the xml api --- .../Runtime/Components/NetworkTransform.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index aa387a614f..640a6952d7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3146,6 +3146,12 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf /// for all transform elements being monitored by /// (i.e. Position, Scale, and Rotation) ///
+ /// + /// All of three max interpolation time properties will have this maximum interpolation bound value applied:
+ /// -
+ /// -
+ /// -
+ ///
/// Maximum time boundary that can be used in a frame when interpolating between two values public void SetMaxInterpolationBound(float maxInterpolationBound) { From 565b4698c2e2a8495404eee2fcc0f8cb8506f32d Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 04:12:26 -0500 Subject: [PATCH 06/50] update Defaulting the lerp smoothing to true (like it was originally when you couldn't enable/disable it). Removing debug script from NetworkManager. --- .../Runtime/Components/NetworkTransform.cs | 6 ++-- .../Runtime/Core/NetworkManager.cs | 35 ------------------- 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 640a6952d7..109b2e8cc4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1046,7 +1046,7 @@ public enum InterpolationTypes /// /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . /// - public bool PositionLerpSmoothing; + public bool PositionLerpSmoothing = true; private bool m_PreviousPositionLerpSmoothing; /// @@ -1069,7 +1069,7 @@ public enum InterpolationTypes /// /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . /// - public bool RotationLerpSmoothing; + public bool RotationLerpSmoothing = true; private bool m_PreviousRotationLerpSmoothing; /// @@ -1092,7 +1092,7 @@ public enum InterpolationTypes /// /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . /// - public bool ScaleLerpSmoothing; + public bool ScaleLerpSmoothing = true; private bool m_PreviousScaleLerpSmoothing; /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index afb91e7f0e..b7d617cb61 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -312,41 +312,6 @@ private void UpdateTopology() IsDistributedAuthority = DistributedAuthorityMode = transportTopology == NetworkTopologyTypes.DistributedAuthority; } } -#if COM_UNITY_MODULES_PHYSICS - internal void CheckNetworkTransformsForDuplicateRegistrations() - { - var uniqueEntries = new Dictionary(); - var uniqueNetworkTransforms = new Dictionary>(); - foreach (var networkObject in NetworkTransformFixedUpdate.Values) - { - if (!uniqueEntries.ContainsKey(networkObject.NetworkObjectId)) - { - uniqueEntries.Add(networkObject.NetworkObjectId, networkObject); - uniqueNetworkTransforms.Add(networkObject.NetworkObjectId, new Dictionary()); - foreach (var networkTransform in networkObject.NetworkTransforms) - { - if (!uniqueNetworkTransforms.ContainsKey(networkTransform.NetworkBehaviourId)) - { - if (!uniqueNetworkTransforms[networkObject.NetworkObjectId].ContainsKey(networkTransform.NetworkBehaviourId)) - { - uniqueNetworkTransforms[networkObject.NetworkObjectId].Add(networkTransform.NetworkBehaviourId, networkTransform); - } - else - { - Debug.LogError($"{networkObject.name}-{networkObject.NetworkObjectId} {nameof(NetworkTransform)}-{networkTransform.NetworkBehaviourId} has a duplicate {nameof(NetworkTransform)} entry!"); - } - } - } - } - else - { - Debug.LogError($"{networkObject.name}-{networkObject.NetworkObjectId} has a duplicate update entry!"); - } - } - Debug.Log($"There are {NetworkTransformFixedUpdate.Count} NetworkTransforms registered for the fixed update."); - Debug.Log($"There are {NetworkTransformUpdate.Count} NetworkTransforms registered for the normal update."); - } -#endif public void NetworkUpdate(NetworkUpdateStage updateStage) { From 92d4ea9ff81204d359e9c52d9a87a0ddb411dd4e Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 04:37:52 -0500 Subject: [PATCH 07/50] style Forgot to remove the components namespace when removing the debug script. --- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b7d617cb61..d564f0f8f3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -9,7 +9,6 @@ #endif using UnityEngine.SceneManagement; using Debug = UnityEngine.Debug; -using Unity.Netcode.Components; namespace Unity.Netcode { From 2bfcf521de77ceb0883a9a665cabf8a1e7da1bf9 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 13:15:50 -0500 Subject: [PATCH 08/50] fix Only apply the InterpolationBufferTickOffset once. --- .../Runtime/Components/NetworkTransform.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 109b2e8cc4..6ba86a3883 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3929,7 +3929,11 @@ private void UpdateInterpolation() #else var cachedDeltaTime = Time.deltaTime; #endif - var tickLatency = m_CachedNetworkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset; + + // Optional user defined tick offset to be used to push the "render time" (the time that will be used to determine if a state update is available) + // back in order to provide more room for the interpolator to interpolate towards when latency conditions are impacting the frequency that state + // updates are received. + var tickLatency = Mathf.Max(1, m_CachedNetworkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset); // If using an owner authoritative motion model if (!IsServerAuthoritative()) @@ -3946,13 +3950,8 @@ private void UpdateInterpolation() } } } - // Optional user defined tick offset to be used to push the "render time" (the time that will be used to determine if a state update is available) - // back in order to provide more room for the interpolator to interpolate towards when latency conditions are impacting the frequency that state - // updates are received. - tickLatency += InterpolationBufferTickOffset; var tickLatencyAsTime = m_CachedNetworkManager.LocalTime.TimeTicksAgo(tickLatency).Time; - // Smooth dampening and extrapolation specific: // We clamp between the tick rate frequency and the tick latency x tick rate frequency var minDeltaTime = m_CachedNetworkManager.LocalTime.FixedDeltaTime; From 0d1f8eef817ccf7e6031fb6f7704e97da9541751 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 13:24:24 -0500 Subject: [PATCH 09/50] update Bumping the default tick latency down to 1. --- .../Runtime/Timing/NetworkTimeSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index c3fc9e8b67..54c7736b58 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -85,7 +85,7 @@ public class NetworkTimeSystem /// For a distributed authority network topology, this latency is between /// the client and the distributed authority service instance. /// - public int TickLatency = 2; + public int TickLatency = 1; internal double LastSyncedServerTimeSec { get; private set; } internal double LastSyncedRttSec { get; private set; } From 2d53a3ae02aa21e01ad842f70d1fe4d2b079846f Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 18:35:46 -0500 Subject: [PATCH 10/50] fix Fixing some additional issues and trying to find a reasonable balance. --- .../BufferedLinearInterpolator.cs | 89 +++++++++++-------- .../Runtime/Components/NetworkTransform.cs | 13 ++- 2 files changed, 60 insertions(+), 42 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 6a161f672d..3aab06ad4e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -118,10 +118,10 @@ internal struct CurrentState public double StartTime; public double EndTime; - public float TimeToTargetValue; - public float DeltaTime; - public float DeltaTimePredict; - public float MaxDeltaTime; + public double TimeToTargetValue; + public double DeltaTime; + public double DeltaTimePredict; + public double MaxDeltaTime; public float LerpT; public float LerpTPredict; public bool TargetReached; @@ -130,11 +130,13 @@ internal struct CurrentState public T PreviousValue; public T PredictValue; public T PredictTarget; + public T Phase1Value; + public T Phase2Value; private float m_AverageDeltaTime; public float AverageDeltaTime => m_AverageDeltaTime; - public float FinalTimeToTarget => Mathf.Max(0.0f, TimeToTargetValue - DeltaTime); + public double FinalTimeToTarget => Math.Max(0.0, TimeToTargetValue - DeltaTime); public void AddDeltaTime(float deltaTime) { @@ -147,12 +149,14 @@ public void AddDeltaTime(float deltaTime) m_AverageDeltaTime += deltaTime; m_AverageDeltaTime *= 0.5f; } - DeltaTime = Math.Min(DeltaTime + m_AverageDeltaTime, TimeToTargetValue); - DeltaTimePredict = Math.Min(DeltaTime + DeltaTimePredict, TimeToTargetValue + MaxDeltaTime); - LerpT = TimeToTargetValue == 0.0f ? 1.0f : DeltaTime / TimeToTargetValue; + DeltaTime = Math.Min(DeltaTime + deltaTime, TimeToTargetValue); + DeltaTimePredict = Math.Min(DeltaTime + deltaTime, TimeToTargetValue + MaxDeltaTime); + //DeltaTime = Math.Min(DeltaTime + m_AverageDeltaTime, TimeToTargetValue); + //DeltaTimePredict = Math.Min(DeltaTimePredict + m_AverageDeltaTime, TimeToTargetValue + MaxDeltaTime); + LerpT = (float)(TimeToTargetValue == 0.0 ? 1.0 : DeltaTime / TimeToTargetValue); if (PredictingNext) { - LerpTPredict = TimeToTargetValue == 0.0f ? 1.0f : DeltaTimePredict / TimeToTargetValue; + LerpTPredict = (float)(TimeToTargetValue == 0.0 ? 1.0 : DeltaTimePredict / TimeToTargetValue); } else { @@ -160,7 +164,7 @@ public void AddDeltaTime(float deltaTime) } } - public void SetTimeToTarget(float timeToTarget) + public void SetTimeToTarget(double timeToTarget) { DeltaTimePredict = 0.0f; LerpTPredict = 0.0f; @@ -191,6 +195,8 @@ public void Reset(T currentValue) PreviousValue = currentValue; PredictValue = currentValue; PredictTarget = currentValue; + Phase1Value = currentValue; + Phase2Value = currentValue; TargetReached = false; PredictingNext = false; LerpT = 0.0f; @@ -304,7 +310,7 @@ private void InternalReset(T targetValue, double serverTime, bool isAngularValue /// render time: the time in "ticks ago" relative to the current tick latency /// minimum time delta (defaults to tick frequency) /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer - private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime, bool isPredictedLerp) + private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double maxDeltaTime, bool isPredictedLerp) { BufferedItem? previousItem = null; var startTime = 0.0; @@ -348,6 +354,8 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m alreadyHasBufferItem = true; InterpolateState.PredictValue = InterpolateState.CurrentValue; InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.Phase1Value = InterpolateState.CurrentValue; + InterpolateState.Phase2Value = InterpolateState.CurrentValue; InterpolateState.SetTimeToTarget(minDeltaTime); InterpolateState.TimeToTargetValue = minDeltaTime; startTime = InterpolateState.Target.Value.TimeSent; @@ -363,17 +371,19 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m InterpolateState.TargetReached = false; InterpolateState.PredictingNext = false; startTime = InterpolateState.Target.Value.TimeSent; + InterpolateState.Phase1Value = InterpolateState.PreviousValue; + InterpolateState.Phase2Value = InterpolateState.PredictValue; InterpolateState.PredictTarget = target.Item; InterpolateState.MaxDeltaTime = maxDeltaTime; - if (m_BufferQueue.TryPeek(out BufferedItem lookAheadItem)) - { - InterpolateState.PredictTarget = Interpolate(target.Item, lookAheadItem.Item, InterpolateState.AverageDeltaTime); - InterpolateState.PredictingNext = true; - } + } + if (m_BufferQueue.TryPeek(out BufferedItem lookAheadItem)) + { + InterpolateState.PredictTarget = Interpolate(target.Item, lookAheadItem.Item, InterpolateState.AverageDeltaTime); + InterpolateState.PredictingNext = true; } // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated // for each item as opposed to losing the resolution of the values. - var timeToTarget = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); + var timeToTarget = Math.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); InterpolateState.SetTimeToTarget(timeToTarget); InterpolateState.Target = target; } @@ -415,7 +425,7 @@ internal void ResetCurrentState() /// Determines whether to use smooth dampening or extrapolation. /// Determines if lerp smoothing is enabled for this instance. /// The newly interpolated value of type 'T' - internal T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, float maxDeltaTime, bool isLerpAndExtrapolate, bool lerpSmoothing) + internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime, double maxDeltaTime, bool isLerpAndExtrapolate, bool lerpSmoothing) { TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime, isLerpAndExtrapolate); // Only begin interpolation when there is a start and end point @@ -429,27 +439,36 @@ internal T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, // SmoothDampen or LerpExtrapolateBlend if (!isLerpAndExtrapolate) { - InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime); - var predictedTime = InterpolateState.PredictingNext ? InterpolateState.DeltaTime : Mathf.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); - InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.PredictTarget, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, predictedTime); + //InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); + //var predictedTime = InterpolateState.PredictingNext ? InterpolateState.DeltaTime : Math.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); + //InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.PredictTarget, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)predictedTime); + InterpolateState.Phase1Value = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); + var predictedTime = InterpolateState.PredictingNext ? InterpolateState.DeltaTime : Math.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); + InterpolateState.PredictValue = SmoothDamp(InterpolateState.Phase1Value, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)predictedTime); + // Determine if smooth dampening is enabled to get our lerp "t" time + var timeDelta = lerpSmoothing ? deltaTime / MaximumInterpolationTime : deltaTime; + // Lerp between the PreviousValue and PredictedValue using the calculated time delta + InterpolateState.CurrentValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, timeDelta); } else { - InterpolateState.PreviousValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); - InterpolateState.PredictValue = InterpolateUnclamped(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, InterpolateState.LerpTPredict); - } + InterpolateState.PreviousValue = Interpolate(InterpolateState.Phase1Value, InterpolateState.Target.Value.Item, InterpolateState.LerpT); - // Lerp between the PreviousValue and PredictedValue (extrapolated) using this frame's delta time - var targetValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime); - if (lerpSmoothing) - { - // If lerp smoothing is enabled, then smooth current value towards the target value - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / MaximumInterpolationTime); - } - else - { - // Otherwise, just assign the target value. - InterpolateState.CurrentValue = targetValue; + // Note: InterpolateState.LerpTPredict is clamped to LerpT if we have no next target + InterpolateState.PredictValue = InterpolateUnclamped(InterpolateState.Phase2Value, InterpolateState.Target.Value.Item, InterpolateState.LerpTPredict); + + // Lerp between the PreviousValue and PredictedValue using this frame's delta time + var targetValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime); + if (lerpSmoothing) + { + // If lerp smoothing is enabled, then smooth current value towards the target value + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / MaximumInterpolationTime); + } + else + { + // Otherwise, just assign the target value. + InterpolateState.CurrentValue = targetValue; + } } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 6ba86a3883..f8569e5075 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3923,13 +3923,12 @@ private void UpdateInterpolation() { AdjustForChangeInTransformSpace(); - var cachedServerTime = m_CachedNetworkManager.ServerTime.Time; + var currentTime = m_CachedNetworkManager.ServerTime.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; #else var cachedDeltaTime = Time.deltaTime; #endif - // Optional user defined tick offset to be used to push the "render time" (the time that will be used to determine if a state update is available) // back in order to provide more room for the interpolator to interpolate towards when latency conditions are impacting the frequency that state // updates are received. @@ -3951,10 +3950,10 @@ private void UpdateInterpolation() } } - var tickLatencyAsTime = m_CachedNetworkManager.LocalTime.TimeTicksAgo(tickLatency).Time; + var tickLatencyAsTime = m_CachedNetworkManager.ServerTime.TimeTicksAgo(tickLatency).Time; // Smooth dampening and extrapolation specific: // We clamp between the tick rate frequency and the tick latency x tick rate frequency - var minDeltaTime = m_CachedNetworkManager.LocalTime.FixedDeltaTime; + var minDeltaTime = m_CachedNetworkManager.ServerTime.FixedDeltaTimeAsDouble; // Maximum delta time is the maximum time we will lerp between values. If the time exceeds this due to extreme // latency then the value's interpolation rate will be accelerated to reach the goal and continue interpolating @@ -3979,7 +3978,7 @@ private void UpdateInterpolation() if (PositionInterpolationType == InterpolationTypes.Lerp) { - m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime, PositionLerpSmoothing); + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime, PositionLerpSmoothing); } else { @@ -4009,7 +4008,7 @@ private void UpdateInterpolation() // When using full precision Slerp towards the target rotation. /// m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; - m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime, RotationLerpSmoothing); + m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime, RotationLerpSmoothing); } else { @@ -4035,7 +4034,7 @@ private void UpdateInterpolation() if (ScaleInterpolationType == InterpolationTypes.Lerp) { - m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime, ScaleLerpSmoothing); + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime, ScaleLerpSmoothing); } else { From 55a0e87c56cf6f1a304a50e9a659958db58bd6a9 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 18:38:02 -0500 Subject: [PATCH 11/50] test Updating NetworkTransformGeneral to test all interpolator types and adding a means to getting verbose debug logs, --- .../Runtime/NetcodeIntegrationTest.cs | 3 ++ .../NetworkTransformGeneral.cs | 30 ++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 95bf441e4d..c7fb34e0ef 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -38,6 +38,7 @@ public enum SceneManagementState } private StringBuilder m_InternalErrorLog = new StringBuilder(); + protected StringBuilder m_VerboseDebugLog = new StringBuilder(); /// /// Registered list of all NetworkObjects spawned. @@ -257,6 +258,7 @@ protected void VerboseDebug(string msg) { if (m_EnableVerboseDebug) { + m_VerboseDebugLog.AppendLine(msg); Debug.Log(msg); } } @@ -345,6 +347,7 @@ protected virtual void OnInlineSetup() [UnitySetUp] public IEnumerator SetUp() { + m_VerboseDebugLog.Clear(); VerboseDebug($"Entering {nameof(SetUp)}"); NetcodeLogAssert = new NetcodeLogAssert(); if (m_EnableTimeTravel) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index efb1ce1244..93c84fd30b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -8,6 +8,8 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] internal class NetworkTransformGeneral : NetworkTransformBase { public NetworkTransformGeneral(HostOrServer testWithHost, Authority authority, NetworkTransform.InterpolationTypes interpolationType) : @@ -298,20 +300,20 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati var newScale = new Vector3(2.0f, 2.0f, 2.0f); m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 800); - Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!"); + Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!\n {m_VerboseDebugLog}"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); m_NonAuthoritativeTransform.SetState(null, newRotation, null, interpolate); success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles), 800); - Assert.True(success, $"Timed out waiting for non-authoritative rotation state request to be applied!"); - Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), "Authoritative rotation does not match!"); - Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), "Non-Authoritative rotation does not match!"); + Assert.True(success, $"Timed out waiting for non-authoritative rotation state request to be applied!\n {m_VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), $"Authoritative rotation does not match!\n {m_VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {m_VerboseDebugLog}"); m_NonAuthoritativeTransform.SetState(null, null, newScale, interpolate); success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale), 800); - Assert.True(success, $"Timed out waiting for non-authoritative scale state request to be applied!"); - Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), "Authoritative scale does not match!"); - Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), "Non-Authoritative scale does not match!"); + Assert.True(success, $"Timed out waiting for non-authoritative scale state request to be applied!\n {m_VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), $"Authoritative scale does not match!\n {m_VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), $"Non-Authoritative scale does not match!\n {m_VerboseDebugLog}"); // Test all parameters at once newPosition = new Vector3(-10f, 95f, -25f); @@ -320,13 +322,13 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati m_NonAuthoritativeTransform.SetState(newPosition, newRotation, newScale, interpolate); success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), 800); - Assert.True(success, $"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!"); - Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); - Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); - Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), "Authoritative rotation does not match!"); - Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), "Non-Authoritative rotation does not match!"); - Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), "Authoritative scale does not match!"); - Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), "Non-Authoritative scale does not match!"); + Assert.True(success, $"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!\n {m_VerboseDebugLog}"); + Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), $"Authoritative position does not match!\n {m_VerboseDebugLog}"); + Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), $"Non-Authoritative position does not match!\n {m_VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), $"Authoritative rotation does not match!\n {m_VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {m_VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), $"Authoritative scale does not match!\n {m_VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), $"Non-Authoritative scale does not match!\n {m_VerboseDebugLog}"); } } } From 29f5e468aef19597d169000a1d36ac65b111bd96 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 18:46:58 -0500 Subject: [PATCH 12/50] fix Fixing a mistake on SD final lerp pass. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 3aab06ad4e..a1143e5802 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -442,9 +442,9 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime //InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); //var predictedTime = InterpolateState.PredictingNext ? InterpolateState.DeltaTime : Math.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); //InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.PredictTarget, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)predictedTime); - InterpolateState.Phase1Value = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); + InterpolateState.PreviousValue = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); var predictedTime = InterpolateState.PredictingNext ? InterpolateState.DeltaTime : Math.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); - InterpolateState.PredictValue = SmoothDamp(InterpolateState.Phase1Value, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)predictedTime); + InterpolateState.PredictValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)predictedTime); // Determine if smooth dampening is enabled to get our lerp "t" time var timeDelta = lerpSmoothing ? deltaTime / MaximumInterpolationTime : deltaTime; // Lerp between the PreviousValue and PredictedValue using the calculated time delta From cfcd70b34df2b33fd5c31616a7e4611ef291a513 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 19:24:37 -0500 Subject: [PATCH 13/50] test making the added verbose log history internal to avoid PVP issue. --- .../Runtime/NetcodeIntegrationTest.cs | 6 ++-- .../NetworkTransformGeneral.cs | 29 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index c7fb34e0ef..e8eb279dcc 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -38,7 +38,7 @@ public enum SceneManagementState } private StringBuilder m_InternalErrorLog = new StringBuilder(); - protected StringBuilder m_VerboseDebugLog = new StringBuilder(); + internal StringBuilder VerboseDebugLog = new StringBuilder(); /// /// Registered list of all NetworkObjects spawned. @@ -258,7 +258,7 @@ protected void VerboseDebug(string msg) { if (m_EnableVerboseDebug) { - m_VerboseDebugLog.AppendLine(msg); + VerboseDebugLog.AppendLine(msg); Debug.Log(msg); } } @@ -347,7 +347,7 @@ protected virtual void OnInlineSetup() [UnitySetUp] public IEnumerator SetUp() { - m_VerboseDebugLog.Clear(); + VerboseDebugLog.Clear(); VerboseDebug($"Entering {nameof(SetUp)}"); NetcodeLogAssert = new NetcodeLogAssert(); if (m_EnableTimeTravel) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 93c84fd30b..9c720ca9f8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -300,20 +300,21 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati var newScale = new Vector3(2.0f, 2.0f, 2.0f); m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 800); - Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!\n {m_VerboseDebugLog}"); + Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!\n {VerboseDebugLog}"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); m_NonAuthoritativeTransform.SetState(null, newRotation, null, interpolate); success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles), 800); - Assert.True(success, $"Timed out waiting for non-authoritative rotation state request to be applied!\n {m_VerboseDebugLog}"); - Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), $"Authoritative rotation does not match!\n {m_VerboseDebugLog}"); - Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {m_VerboseDebugLog}"); + Assert.True(success, $"Timed out waiting for non-authoritative rotation state request to be applied!\n {VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), $"Authoritative rotation does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {VerboseDebugLog}"); m_NonAuthoritativeTransform.SetState(null, null, newScale, interpolate); success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale), 800); - Assert.True(success, $"Timed out waiting for non-authoritative scale state request to be applied!\n {m_VerboseDebugLog}"); - Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), $"Authoritative scale does not match!\n {m_VerboseDebugLog}"); - Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), $"Non-Authoritative scale does not match!\n {m_VerboseDebugLog}"); + Assert.True(success, $"Timed out waiting for non-authoritative scale state request to be applied!\n {VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), $"Authoritative scale does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), $"Non-Authoritative scale does not match!\n {VerboseDebugLog}"); // Test all parameters at once newPosition = new Vector3(-10f, 95f, -25f); @@ -322,13 +323,13 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati m_NonAuthoritativeTransform.SetState(newPosition, newRotation, newScale, interpolate); success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), 800); - Assert.True(success, $"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!\n {m_VerboseDebugLog}"); - Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), $"Authoritative position does not match!\n {m_VerboseDebugLog}"); - Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), $"Non-Authoritative position does not match!\n {m_VerboseDebugLog}"); - Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), $"Authoritative rotation does not match!\n {m_VerboseDebugLog}"); - Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {m_VerboseDebugLog}"); - Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), $"Authoritative scale does not match!\n {m_VerboseDebugLog}"); - Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), $"Non-Authoritative scale does not match!\n {m_VerboseDebugLog}"); + Assert.True(success, $"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!\n {VerboseDebugLog}"); + Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), $"Authoritative position does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), $"Non-Authoritative position does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), $"Authoritative rotation does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), $"Authoritative scale does not match!\n {VerboseDebugLog}"); + Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), $"Non-Authoritative scale does not match!\n {VerboseDebugLog}"); } } } From d87bd754e31cccc41abbee7babf182b6f556dacf Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 19:28:27 -0500 Subject: [PATCH 14/50] fix making sure the first entry is set to having reached its final point. --- .../Interpolator/BufferedLinearInterpolator.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index a1143e5802..954641a02b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -352,6 +352,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double InterpolateState.Target = target; alreadyHasBufferItem = true; + InterpolateState.CurrentValue = target.Item; InterpolateState.PredictValue = InterpolateState.CurrentValue; InterpolateState.PreviousValue = InterpolateState.CurrentValue; InterpolateState.Phase1Value = InterpolateState.CurrentValue; @@ -359,8 +360,10 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double InterpolateState.SetTimeToTarget(minDeltaTime); InterpolateState.TimeToTargetValue = minDeltaTime; startTime = InterpolateState.Target.Value.TimeSent; - InterpolateState.TargetReached = false; + InterpolateState.TargetReached = true; InterpolateState.PredictingNext = false; + InterpolateState.LerpT = 1.0f; + InterpolateState.LerpTPredict = 1.0f; InterpolateState.MaxDeltaTime = maxDeltaTime; } else @@ -442,9 +445,9 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime //InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); //var predictedTime = InterpolateState.PredictingNext ? InterpolateState.DeltaTime : Math.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); //InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.PredictTarget, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)predictedTime); - InterpolateState.PreviousValue = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); + InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); var predictedTime = InterpolateState.PredictingNext ? InterpolateState.DeltaTime : Math.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); - InterpolateState.PredictValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)predictedTime); + InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)predictedTime); // Determine if smooth dampening is enabled to get our lerp "t" time var timeDelta = lerpSmoothing ? deltaTime / MaximumInterpolationTime : deltaTime; // Lerp between the PreviousValue and PredictedValue using the calculated time delta From 4170dfe717de51cd1ac59e7b2ccb42a52701c132 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 20:15:30 -0500 Subject: [PATCH 15/50] test Adding debug information to better understand the failure. --- .../NetworkTransform/NetworkTransformBase.cs | 17 +++++++++++++++-- .../NetworkTransform/NetworkTransformGeneral.cs | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index 4413b73fee..6658a70845 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -632,14 +632,27 @@ protected bool PositionsMatchesValue(Vector3 positionToMatch) var nonAuthorityPosition = m_NonAuthoritativeTransform.transform.position; var auhtorityIsEqual = Approximately(authorityPosition, positionToMatch); var nonauthorityIsEqual = Approximately(nonAuthorityPosition, positionToMatch); + var authorityTimeAndTick = string.Empty; + var nonAuthorityTimeAndTick = string.Empty; + + if (m_AuthoritativeTransform.NetworkManager) + { + var authTime = m_AuthoritativeTransform.NetworkManager.LocalTime; + authorityTimeAndTick = $"[{authTime.Time}[{authTime.Tick}]]"; + } + if (m_NonAuthoritativeTransform.NetworkManager) + { + var nonAuthTime = m_NonAuthoritativeTransform.NetworkManager.LocalTime; + nonAuthorityTimeAndTick = $"[{nonAuthTime.Time}[{nonAuthTime.Tick}]]"; + } if (!auhtorityIsEqual) { - VerboseDebug($"Authority position {authorityPosition} != position to match: {positionToMatch}!"); + VerboseDebug($"[{authorityTimeAndTick}] Authority position {authorityPosition} != position to match: {positionToMatch} [{nonAuthorityTimeAndTick}]!"); } if (!nonauthorityIsEqual) { - VerboseDebug($"NonAuthority position {nonAuthorityPosition} != position to match: {positionToMatch}!"); + VerboseDebug($"[{authorityTimeAndTick}] NonAuthority position {nonAuthorityPosition} != position to match: {positionToMatch} [{nonAuthorityTimeAndTick}]!"); } return auhtorityIsEqual && nonauthorityIsEqual; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 9c720ca9f8..9a6f0a1f01 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -289,7 +289,7 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 0.1f; m_EnableVerboseDebug = true; - + VerboseDebug($"Target Frame Rate: {Application.targetFrameRate}"); m_AuthoritativeTransform.Teleport(Vector3.zero, Quaternion.identity, Vector3.one); var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(Vector3.zero, Quaternion.identity.eulerAngles, Vector3.one), 800); Assert.True(success, $"Timed out waiting for initialization to be applied!"); From d2b552ed23d199f326e532045ca24f2ca829dd9a Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 22:32:15 -0500 Subject: [PATCH 16/50] update Sigh... --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 954641a02b..eed7bfaf4b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -166,6 +166,7 @@ public void AddDeltaTime(float deltaTime) public void SetTimeToTarget(double timeToTarget) { + m_AverageDeltaTime = 0.0f; DeltaTimePredict = 0.0f; LerpTPredict = 0.0f; LerpT = 0.0f; @@ -350,9 +351,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double if (!InterpolateState.Target.HasValue) { InterpolateState.Target = target; - alreadyHasBufferItem = true; - InterpolateState.CurrentValue = target.Item; InterpolateState.PredictValue = InterpolateState.CurrentValue; InterpolateState.PreviousValue = InterpolateState.CurrentValue; InterpolateState.Phase1Value = InterpolateState.CurrentValue; @@ -360,10 +359,8 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double InterpolateState.SetTimeToTarget(minDeltaTime); InterpolateState.TimeToTargetValue = minDeltaTime; startTime = InterpolateState.Target.Value.TimeSent; - InterpolateState.TargetReached = true; + InterpolateState.TargetReached = false; InterpolateState.PredictingNext = false; - InterpolateState.LerpT = 1.0f; - InterpolateState.LerpTPredict = 1.0f; InterpolateState.MaxDeltaTime = maxDeltaTime; } else From 1efdc3b6ba0515e929e84d75d90f88049178cec7 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 21 Mar 2025 22:32:29 -0500 Subject: [PATCH 17/50] Update Removing the teleport I put in last week. --- .../Runtime/NetworkTransform/NetworkTransformGeneral.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 9a6f0a1f01..b079a3d19e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -290,16 +290,17 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati m_EnableVerboseDebug = true; VerboseDebug($"Target Frame Rate: {Application.targetFrameRate}"); - m_AuthoritativeTransform.Teleport(Vector3.zero, Quaternion.identity, Vector3.one); - var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(Vector3.zero, Quaternion.identity.eulerAngles, Vector3.one), 800); - Assert.True(success, $"Timed out waiting for initialization to be applied!"); + //m_AuthoritativeTransform.Teleport(Vector3.zero, Quaternion.identity, Vector3.one); + //TimeTravelAdvanceTick(); + //var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(Vector3.zero, Quaternion.identity.eulerAngles, Vector3.one), 800); + //Assert.True(success, $"Timed out waiting for initialization to be applied!"); // Test one parameter at a time first var newPosition = new Vector3(55f, 35f, 65f); var newRotation = Quaternion.Euler(1, 2, 3); var newScale = new Vector3(2.0f, 2.0f, 2.0f); m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 800); + var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 800); Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!\n {VerboseDebugLog}"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); From bdbb218d6903bdae3f341186d6ded8a793e2c6ae Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 22 Mar 2025 12:22:48 -0500 Subject: [PATCH 18/50] test Dumb floating point precision. --- .../Tests/Runtime/TransformInterpolationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 4e0cea8e53..840204d4a5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -14,7 +14,7 @@ internal class TransformInterpolationObject : NetworkTransform public static bool TestComplete = false; // Set the minimum threshold which we will use as our margin of error #if UNITY_EDITOR - public const float MinThreshold = 0.005f; + public const float MinThreshold = 0.00555555f; #else // Add additional room for error on console tests public const float MinThreshold = 0.009999f; From b398f9383a625991c5ce0df38ce0532192f48d6c Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 22 Mar 2025 12:23:48 -0500 Subject: [PATCH 19/50] update Minor adjustment UpdateInterpolation --- .../Runtime/Components/NetworkTransform.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index f8569e5075..c30fbb8a0a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3922,8 +3922,8 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca private void UpdateInterpolation() { AdjustForChangeInTransformSpace(); - - var currentTime = m_CachedNetworkManager.ServerTime.Time; + var timeSystem = m_CachedNetworkManager.ServerTime; + var currentTime = timeSystem.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; #else @@ -3950,10 +3950,10 @@ private void UpdateInterpolation() } } - var tickLatencyAsTime = m_CachedNetworkManager.ServerTime.TimeTicksAgo(tickLatency).Time; + var tickLatencyAsTime = timeSystem.TimeTicksAgo(tickLatency).Time; // Smooth dampening and extrapolation specific: // We clamp between the tick rate frequency and the tick latency x tick rate frequency - var minDeltaTime = m_CachedNetworkManager.ServerTime.FixedDeltaTimeAsDouble; + var minDeltaTime = timeSystem.FixedDeltaTimeAsDouble; // Maximum delta time is the maximum time we will lerp between values. If the time exceeds this due to extreme // latency then the value's interpolation rate will be accelerated to reach the goal and continue interpolating From ed45dc937a356ccf0f0b8f8308fb8ac880b03c49 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 22 Mar 2025 19:44:47 -0500 Subject: [PATCH 20/50] update Removing commented out code. NetworkTransform using the realtime provider time system to be more compatible with the integration test time travel system. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 3 --- .../Runtime/Components/NetworkTransform.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index eed7bfaf4b..6952637048 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -439,9 +439,6 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime // SmoothDampen or LerpExtrapolateBlend if (!isLerpAndExtrapolate) { - //InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); - //var predictedTime = InterpolateState.PredictingNext ? InterpolateState.DeltaTime : Math.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); - //InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.PredictTarget, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)predictedTime); InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); var predictedTime = InterpolateState.PredictingNext ? InterpolateState.DeltaTime : Math.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)predictedTime); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index c30fbb8a0a..79b3bfaef9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3925,7 +3925,7 @@ private void UpdateInterpolation() var timeSystem = m_CachedNetworkManager.ServerTime; var currentTime = timeSystem.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; + var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; #else var cachedDeltaTime = Time.deltaTime; #endif From 4c36581d0cf16d7a9c9f2ae151f1eae96ea9482c Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 22 Mar 2025 19:46:39 -0500 Subject: [PATCH 21/50] test providing a bit more time and reducing the target position to provide enough time to for interpolation. --- .../NetworkTransform/NetworkTransformGeneral.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index b079a3d19e..6ee8d12617 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -296,23 +296,23 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati //Assert.True(success, $"Timed out waiting for initialization to be applied!"); // Test one parameter at a time first - var newPosition = new Vector3(55f, 35f, 65f); + var newPosition = new Vector3(15f,-12f, 10f); var newRotation = Quaternion.Euler(1, 2, 3); var newScale = new Vector3(2.0f, 2.0f, 2.0f); m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); - var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 800); + var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 1000); Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!\n {VerboseDebugLog}"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); m_NonAuthoritativeTransform.SetState(null, newRotation, null, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles), 800); + success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles), 1000); Assert.True(success, $"Timed out waiting for non-authoritative rotation state request to be applied!\n {VerboseDebugLog}"); Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), $"Authoritative rotation does not match!\n {VerboseDebugLog}"); Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {VerboseDebugLog}"); Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {VerboseDebugLog}"); m_NonAuthoritativeTransform.SetState(null, null, newScale, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale), 800); + success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale), 1000); Assert.True(success, $"Timed out waiting for non-authoritative scale state request to be applied!\n {VerboseDebugLog}"); Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), $"Authoritative scale does not match!\n {VerboseDebugLog}"); Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), $"Non-Authoritative scale does not match!\n {VerboseDebugLog}"); @@ -323,7 +323,7 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati newScale = new Vector3(0.5f, 0.5f, 0.5f); m_NonAuthoritativeTransform.SetState(newPosition, newRotation, newScale, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), 800); + success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), 1000); Assert.True(success, $"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!\n {VerboseDebugLog}"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), $"Authoritative position does not match!\n {VerboseDebugLog}"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), $"Non-Authoritative position does not match!\n {VerboseDebugLog}"); From 233188ce80cf5c200bcb8688270af5b7e0047189 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 22 Mar 2025 20:53:12 -0500 Subject: [PATCH 22/50] test Removing some of the debug information. Fixing whitespace issue. Adding a smooth lerp and non-smooth lerp pass. --- .../NetworkTransform/NetworkTransformBase.cs | 18 ++---------- .../NetworkTransformGeneral.cs | 29 ++++++++++++------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index 6658a70845..e3fe4f4ce3 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -632,27 +632,13 @@ protected bool PositionsMatchesValue(Vector3 positionToMatch) var nonAuthorityPosition = m_NonAuthoritativeTransform.transform.position; var auhtorityIsEqual = Approximately(authorityPosition, positionToMatch); var nonauthorityIsEqual = Approximately(nonAuthorityPosition, positionToMatch); - var authorityTimeAndTick = string.Empty; - var nonAuthorityTimeAndTick = string.Empty; - - if (m_AuthoritativeTransform.NetworkManager) - { - var authTime = m_AuthoritativeTransform.NetworkManager.LocalTime; - authorityTimeAndTick = $"[{authTime.Time}[{authTime.Tick}]]"; - } - if (m_NonAuthoritativeTransform.NetworkManager) - { - var nonAuthTime = m_NonAuthoritativeTransform.NetworkManager.LocalTime; - nonAuthorityTimeAndTick = $"[{nonAuthTime.Time}[{nonAuthTime.Tick}]]"; - } - if (!auhtorityIsEqual) { - VerboseDebug($"[{authorityTimeAndTick}] Authority position {authorityPosition} != position to match: {positionToMatch} [{nonAuthorityTimeAndTick}]!"); + VerboseDebug($"Authority ({m_AuthoritativeTransform.name}) position {authorityPosition} != position to match: {positionToMatch}!"); } if (!nonauthorityIsEqual) { - VerboseDebug($"[{authorityTimeAndTick}] NonAuthority position {nonAuthorityPosition} != position to match: {positionToMatch} [{nonAuthorityTimeAndTick}]!"); + VerboseDebug($"NonAuthority ({m_NonAuthoritativeTransform.name}) position {nonAuthorityPosition} != position to match: {positionToMatch}!"); } return auhtorityIsEqual && nonauthorityIsEqual; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 6ee8d12617..24b348a0cb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -12,6 +12,12 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] internal class NetworkTransformGeneral : NetworkTransformBase { + public enum SmoothLerpSettings + { + SmoothLerp, + NormalLerp + } + public NetworkTransformGeneral(HostOrServer testWithHost, Authority authority, NetworkTransform.InterpolationTypes interpolationType) : base(testWithHost, authority, RotationCompression.None, Rotation.Euler, Precision.Full) { @@ -281,38 +287,39 @@ public void TestMultipleExplicitSetStates([Values] Interpolation interpolation) /// This also tests that the original server authoritative model with client-owner driven NetworkTransforms is preserved. /// [Test] - public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolation) + public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolation, [Values] SmoothLerpSettings smoothLerp) { var interpolate = interpolation == Interpolation.EnableInterpolate; + var usingSmoothLerp = (smoothLerp == SmoothLerpSettings.SmoothLerp) && interpolate; + var waitForDelay = usingSmoothLerp ? 1000 : 500; + m_NonAuthoritativeTransform.PositionLerpSmoothing = usingSmoothLerp; + m_NonAuthoritativeTransform.RotationLerpSmoothing = usingSmoothLerp; + m_NonAuthoritativeTransform.ScaleLerpSmoothing = usingSmoothLerp; + m_AuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 0.1f; m_EnableVerboseDebug = true; VerboseDebug($"Target Frame Rate: {Application.targetFrameRate}"); - //m_AuthoritativeTransform.Teleport(Vector3.zero, Quaternion.identity, Vector3.one); - //TimeTravelAdvanceTick(); - //var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(Vector3.zero, Quaternion.identity.eulerAngles, Vector3.one), 800); - //Assert.True(success, $"Timed out waiting for initialization to be applied!"); - // Test one parameter at a time first - var newPosition = new Vector3(15f,-12f, 10f); + var newPosition = usingSmoothLerp ? new Vector3(15f, -12f, 10f) : new Vector3(55f, -24f, 20f); var newRotation = Quaternion.Euler(1, 2, 3); var newScale = new Vector3(2.0f, 2.0f, 2.0f); m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); - var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 1000); + var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), waitForDelay); Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!\n {VerboseDebugLog}"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); m_NonAuthoritativeTransform.SetState(null, newRotation, null, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles), 1000); + success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles), waitForDelay); Assert.True(success, $"Timed out waiting for non-authoritative rotation state request to be applied!\n {VerboseDebugLog}"); Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), $"Authoritative rotation does not match!\n {VerboseDebugLog}"); Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {VerboseDebugLog}"); Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), $"Non-Authoritative rotation does not match!\n {VerboseDebugLog}"); m_NonAuthoritativeTransform.SetState(null, null, newScale, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale), 1000); + success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale), waitForDelay); Assert.True(success, $"Timed out waiting for non-authoritative scale state request to be applied!\n {VerboseDebugLog}"); Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), $"Authoritative scale does not match!\n {VerboseDebugLog}"); Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), $"Non-Authoritative scale does not match!\n {VerboseDebugLog}"); @@ -323,7 +330,7 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati newScale = new Vector3(0.5f, 0.5f, 0.5f); m_NonAuthoritativeTransform.SetState(newPosition, newRotation, newScale, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), 1000); + success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), waitForDelay); Assert.True(success, $"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!\n {VerboseDebugLog}"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), $"Authoritative position does not match!\n {VerboseDebugLog}"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), $"Non-Authoritative position does not match!\n {VerboseDebugLog}"); From e7eaecdcb7f3d27c4eb190394a329c90b5eecd54 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 22 Mar 2025 21:00:41 -0500 Subject: [PATCH 23/50] text Adding additional LerpExtrapolateBlend passes --- .../NetworkTransform/NetworkTransformTests.cs | 21 +++++++++++++++++++ .../NestedNetworkTransformTests.cs | 15 +++++++++++++ 2 files changed, 36 insertions(+) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 6418e0f23f..3aba8baa01 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -11,6 +11,7 @@ namespace Unity.Netcode.RuntimeTests /// models for each operating mode. /// [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] @@ -19,6 +20,12 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] @@ -27,6 +34,7 @@ namespace Unity.Netcode.RuntimeTests #endif [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] @@ -35,6 +43,12 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] @@ -42,6 +56,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] #endif [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] @@ -50,6 +65,12 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] diff --git a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs index 60cbf38082..9dff9e141c 100644 --- a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs @@ -34,6 +34,21 @@ namespace TestProject.RuntimeTests [TestFixture(Interpolation.NoInterpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + + // Lerp, extrapolate, and blend pass + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + // Smooth dampening interpolation pass [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] From 4e705fe2ade1b92fea48996394eebebddff74c3a Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Mon, 24 Mar 2025 02:09:45 -0500 Subject: [PATCH 24/50] update Minor adjustments after a long debug session... --- .../BufferedLinearInterpolator.cs | 66 +++++++------------ .../Runtime/Components/NetworkTransform.cs | 34 +++++++++- 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 6952637048..d267600b1e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -115,7 +115,6 @@ public BufferedItem(T item, double timeSent, int itemId) internal struct CurrentState { public BufferedItem? Target; - public double StartTime; public double EndTime; public double TimeToTargetValue; @@ -129,7 +128,6 @@ internal struct CurrentState public T CurrentValue; public T PreviousValue; public T PredictValue; - public T PredictTarget; public T Phase1Value; public T Phase2Value; @@ -151,8 +149,6 @@ public void AddDeltaTime(float deltaTime) } DeltaTime = Math.Min(DeltaTime + deltaTime, TimeToTargetValue); DeltaTimePredict = Math.Min(DeltaTime + deltaTime, TimeToTargetValue + MaxDeltaTime); - //DeltaTime = Math.Min(DeltaTime + m_AverageDeltaTime, TimeToTargetValue); - //DeltaTimePredict = Math.Min(DeltaTimePredict + m_AverageDeltaTime, TimeToTargetValue + MaxDeltaTime); LerpT = (float)(TimeToTargetValue == 0.0 ? 1.0 : DeltaTime / TimeToTargetValue); if (PredictingNext) { @@ -174,12 +170,6 @@ public void SetTimeToTarget(double timeToTarget) TimeToTargetValue = timeToTarget; } - public void ResetDelta() - { - m_AverageDeltaTime = 0.0f; - DeltaTime = 0.0f; - } - public bool TargetTimeAproximatelyReached(float adjustForNext = 1.0f) { if (!Target.HasValue) @@ -195,7 +185,6 @@ public void Reset(T currentValue) CurrentValue = currentValue; PreviousValue = currentValue; PredictValue = currentValue; - PredictTarget = currentValue; Phase1Value = currentValue; Phase2Value = currentValue; TargetReached = false; @@ -207,7 +196,7 @@ public void Reset(T currentValue) TimeToTargetValue = 0.0f; DeltaTime = 0.0f; DeltaTimePredict = 0.0f; - ResetDelta(); + m_AverageDeltaTime = 0.0f; } } @@ -332,7 +321,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double if (!noStateSet) { potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent >= InterpolateState.Target.Value.TimeSent; - currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing ? 1.15f : 0.85f); + currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing ? 1.25f : 0.75f); if (!potentialItemNeedsProcessing && !InterpolateState.TargetReached) { InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); @@ -371,23 +360,20 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double InterpolateState.TargetReached = false; InterpolateState.PredictingNext = false; startTime = InterpolateState.Target.Value.TimeSent; - InterpolateState.Phase1Value = InterpolateState.PreviousValue; - InterpolateState.Phase2Value = InterpolateState.PredictValue; - InterpolateState.PredictTarget = target.Item; + if (isPredictedLerp) + { + InterpolateState.Phase1Value = InterpolateState.PreviousValue; + InterpolateState.Phase2Value = Interpolate(InterpolateState.Phase1Value, target.Item, InterpolateState.AverageDeltaTime); + } InterpolateState.MaxDeltaTime = maxDeltaTime; } - if (m_BufferQueue.TryPeek(out BufferedItem lookAheadItem)) - { - InterpolateState.PredictTarget = Interpolate(target.Item, lookAheadItem.Item, InterpolateState.AverageDeltaTime); - InterpolateState.PredictingNext = true; - } + InterpolateState.PredictingNext = m_BufferQueue.Count > 0; // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated // for each item as opposed to losing the resolution of the values. var timeToTarget = Math.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); InterpolateState.SetTimeToTarget(timeToTarget); InterpolateState.Target = target; } - InterpolateState.ResetDelta(); } } else @@ -435,37 +421,32 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime if (!InterpolateState.TargetReached) { InterpolateState.AddDeltaTime(deltaTime); - + var targetValue = InterpolateState.CurrentValue; // SmoothDampen or LerpExtrapolateBlend if (!isLerpAndExtrapolate) { InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); - var predictedTime = InterpolateState.PredictingNext ? InterpolateState.DeltaTime : Math.Min(InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); - InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)predictedTime); - // Determine if smooth dampening is enabled to get our lerp "t" time - var timeDelta = lerpSmoothing ? deltaTime / MaximumInterpolationTime : deltaTime; - // Lerp between the PreviousValue and PredictedValue using the calculated time delta - InterpolateState.CurrentValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, timeDelta); + InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)(InterpolateState.DeltaTime + deltaTime)); } else { InterpolateState.PreviousValue = Interpolate(InterpolateState.Phase1Value, InterpolateState.Target.Value.Item, InterpolateState.LerpT); - // Note: InterpolateState.LerpTPredict is clamped to LerpT if we have no next target InterpolateState.PredictValue = InterpolateUnclamped(InterpolateState.Phase2Value, InterpolateState.Target.Value.Item, InterpolateState.LerpTPredict); + } - // Lerp between the PreviousValue and PredictedValue using this frame's delta time - var targetValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime); - if (lerpSmoothing) - { - // If lerp smoothing is enabled, then smooth current value towards the target value - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / MaximumInterpolationTime); - } - else - { - // Otherwise, just assign the target value. - InterpolateState.CurrentValue = targetValue; - } + // Lerp between the PreviousValue and PredictedValue using the calculated time delta + targetValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime); + + if (lerpSmoothing) + { + // If lerp smoothing is enabled, then smooth current value towards the target value + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / MaximumInterpolationTime); + } + else + { + // Otherwise, just assign the target value. + InterpolateState.CurrentValue = targetValue; } } } @@ -522,7 +503,6 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) InterpolateState.EndTime = target.TimeSent; InterpolateState.Target = target; } - InterpolateState.ResetDelta(); } } else diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 79b3bfaef9..5a0b8a71e6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3916,8 +3916,24 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca #region UPDATES AND AUTHORITY CHECKS private NetworkTransformTickRegistration m_NetworkTransformTickRegistration; +#if DEBUG_LINEARBUFFER + public struct NTPositionStats + { + public int Tick; + public double Time; + public double TicksAgoTime; + public int BufferCount; + public BufferedLinearInterpolator.CurrentState CurrentState; + } + public List PositionStats = new List(); + public bool GatherStats; + public void ClearStats() + { + PositionStats.Clear(); + } +#endif // Non-Authority private void UpdateInterpolation() { @@ -3927,7 +3943,7 @@ private void UpdateInterpolation() #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; #else - var cachedDeltaTime = Time.deltaTime; + var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; #endif // Optional user defined tick offset to be used to push the "render time" (the time that will be used to determine if a state update is available) // back in order to provide more room for the interpolator to interpolate towards when latency conditions are impacting the frequency that state @@ -4042,6 +4058,21 @@ private void UpdateInterpolation() ScaleInterpolationType == InterpolationTypes.LerpExtrapolateBlend, ScaleLerpSmoothing); } } + +#if DEBUG_LINEARBUFFER + if (GatherStats) + { + var posStats = new NTPositionStats() + { + Tick = timeSystem.Tick, + Time = timeSystem.Time, + TicksAgoTime = tickLatencyAsTime, + BufferCount = m_PositionInterpolator.m_BufferQueue.Count, + CurrentState = m_PositionInterpolator.InterpolateState, + }; + PositionStats.Add(posStats); + } +#endif } /// @@ -4067,7 +4098,6 @@ public virtual void OnUpdate() UpdateInterpolation(); } - // Apply the current authoritative state ApplyAuthoritativeState(); } From ff552c5ae5568214f0f7c02ff261c1ea67fb1b63 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 25 Mar 2025 19:19:27 -0500 Subject: [PATCH 25/50] update - Removed the lerp smoothing parameter and made it an internal bool (don't need to add to API and it should be set through the NetworkTransform anyway). - Added additional BufferedLinearInterpolator debug code. - Added additional PreUpdate pass for NetworkTransforms using Rigidbody motion in order to reset a fixed time delta. - Added incemental fixed time delta when the FixedUpdate is invoked multiple times within a single frame. - Added internal time sync counter. - Added a "1/3rd" rule to smooth dampening and LerpExtrapolateBlend when smooth lerp is enabled in order to still be able to balance between smoothing the final lerped value and reaching the target (or getting much closer). --- .../BufferedLinearInterpolator.cs | 35 ++-- .../Runtime/Components/NetworkTransform.cs | 157 ++++++++++++++++-- .../Runtime/Core/NetworkManager.cs | 19 +++ .../Messaging/Messages/TimeSyncMessage.cs | 1 + .../Runtime/Timing/NetworkTimeSystem.cs | 12 +- 5 files changed, 192 insertions(+), 32 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index d267600b1e..9005efec39 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -200,6 +200,8 @@ public void Reset(T currentValue) } } + internal bool LerpSmoothEnabled; + /// /// Determines how much smoothing will be applied to the 2nd lerp when using the (i.e. lerping and not smooth dampening). /// @@ -293,7 +295,7 @@ private void InternalReset(T targetValue, double serverTime, bool isAngularValue } } - #region Smooth Dampening Interpolation + #region Smooth Dampening and Lerp Extrapolate Blend Interpolation Handling /// /// TryConsumeFromBuffer: Smooth Dampening Version /// @@ -308,6 +310,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double var noStateSet = !InterpolateState.Target.HasValue; var potentialItemNeedsProcessing = false; var currentTargetTimeReached = false; + var dequeuedCount = 0; while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) { @@ -321,7 +324,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double if (!noStateSet) { potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent >= InterpolateState.Target.Value.TimeSent; - currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing ? 1.25f : 0.75f); + currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing ? 1.0f : 0.85f) || InterpolateState.TargetReached; if (!potentialItemNeedsProcessing && !InterpolateState.TargetReached) { InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); @@ -337,6 +340,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double { if (m_BufferQueue.TryDequeue(out BufferedItem target)) { + dequeuedCount++; if (!InterpolateState.Target.HasValue) { InterpolateState.Target = target; @@ -358,20 +362,18 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double { alreadyHasBufferItem = true; InterpolateState.TargetReached = false; - InterpolateState.PredictingNext = false; startTime = InterpolateState.Target.Value.TimeSent; if (isPredictedLerp) { InterpolateState.Phase1Value = InterpolateState.PreviousValue; - InterpolateState.Phase2Value = Interpolate(InterpolateState.Phase1Value, target.Item, InterpolateState.AverageDeltaTime); + InterpolateState.Phase2Value = Interpolate(InterpolateState.PredictValue, target.Item, InterpolateState.AverageDeltaTime); } InterpolateState.MaxDeltaTime = maxDeltaTime; + InterpolateState.PredictingNext = m_BufferQueue.Count > 0; } - InterpolateState.PredictingNext = m_BufferQueue.Count > 0; // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated // for each item as opposed to losing the resolution of the values. - var timeToTarget = Math.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); - InterpolateState.SetTimeToTarget(timeToTarget); + InterpolateState.SetTimeToTarget(Math.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime * dequeuedCount)); InterpolateState.Target = target; } } @@ -394,6 +396,8 @@ internal void ResetCurrentState() if (InterpolateState.Target.HasValue) { InterpolateState.Reset(InterpolateState.CurrentValue); + m_RateOfChange = default; + m_PredictedRateOfChange = default; } } @@ -411,7 +415,7 @@ internal void ResetCurrentState() /// Determines whether to use smooth dampening or extrapolation. /// Determines if lerp smoothing is enabled for this instance. /// The newly interpolated value of type 'T' - internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime, double maxDeltaTime, bool isLerpAndExtrapolate, bool lerpSmoothing) + internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime, double maxDeltaTime, bool isLerpAndExtrapolate) { TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime, isLerpAndExtrapolate); // Only begin interpolation when there is a start and end point @@ -420,6 +424,8 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime // As long as the target hasn't been reached, interpolate or smooth dampen. if (!InterpolateState.TargetReached) { + // Increases the time delta relative to the time to target. + // Also calculates the LerpT and LerpTPredicted values. InterpolateState.AddDeltaTime(deltaTime); var targetValue = InterpolateState.CurrentValue; // SmoothDampen or LerpExtrapolateBlend @@ -438,10 +444,13 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime // Lerp between the PreviousValue and PredictedValue using the calculated time delta targetValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime); - if (lerpSmoothing) + // If lerp smoothing is enabled, then smooth current value towards the target value + if (LerpSmoothEnabled) { - // If lerp smoothing is enabled, then smooth current value towards the target value - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / MaximumInterpolationTime); + // Progress by 1/3rd of the way towards the target in order to assure the target is reached sooner + var oneThirdPoint = Interpolate(InterpolateState.CurrentValue, targetValue, 0.3333f); + // Apply the smooth lerp from the oneThirpoint to the target to help smooth the final value + InterpolateState.CurrentValue = Interpolate(oneThirdPoint, targetValue, deltaTime / MaximumInterpolationTime); } else { @@ -530,7 +539,7 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) /// current server time /// Determines if lerp smoothing is enabled for this instance. /// The newly interpolated value of type 'T' - public T Update(float deltaTime, double renderTime, double serverTime, bool lerpSmoothing = true) + public T Update(float deltaTime, double renderTime, double serverTime) { TryConsumeFromBuffer(renderTime, serverTime); // Only interpolate when there is a start and end point and we have not already reached the end value @@ -557,7 +566,7 @@ public T Update(float deltaTime, double renderTime, double serverTime, bool lerp } var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, t); - if (lerpSmoothing) + if (LerpSmoothEnabled) { // Assure our MaximumInterpolationTime is valid and that the second lerp time ranges between deltaTime and 1.0f. var secondLerpTime = Mathf.Clamp(deltaTime / MaximumInterpolationTime, deltaTime, 1.0f); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 5a0b8a71e6..0462ccc6d0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -16,7 +16,6 @@ namespace Unity.Netcode.Components [AddComponentMenu("Netcode/Network Transform")] public class NetworkTransform : NetworkBehaviour { - #if UNITY_EDITOR internal virtual bool HideInterpolateValue => false; @@ -3916,24 +3915,49 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca #region UPDATES AND AUTHORITY CHECKS private NetworkTransformTickRegistration m_NetworkTransformTickRegistration; -#if DEBUG_LINEARBUFFER +#if DEBUG_LINEARBUFFER && UNITY_EDITOR + // For debugging purposes + public struct BufferEntry + { + public double TimeSent; + public Vector3 Position; + } public struct NTPositionStats { + public int FrameCount; + public int FixedFrameCount; + public int FixedUpdatesPerFrameCount; + public int TimeSynchCount; public int Tick; public double Time; public double TicksAgoTime; public int BufferCount; - public BufferedLinearInterpolator.CurrentState CurrentState; + public int TickMeasured; + public float LerpT; + public float LerpTPredict; + public double DeltaTime; + public double DeltaTimePredict; + public double MaxDeltaTime; + public double TimeSent; + public double TimeToTargetValue; + public Vector3 PreviousValue; + public Vector3 PredictValue; + public Vector3 CurrentValue; + public Vector3 TargetValue; + + public List Buffer; } - public List PositionStats = new List(); + public Dictionary> PositionStats = new Dictionary>(); public bool GatherStats; + private int m_LastTimeSyncCount; public void ClearStats() { PositionStats.Clear(); } #endif + // Non-Authority private void UpdateInterpolation() { @@ -3966,7 +3990,19 @@ private void UpdateInterpolation() } } + // Get the tick latency (ticks ago) as time (in the past) to process state updates in the queue. var tickLatencyAsTime = timeSystem.TimeTicksAgo(tickLatency).Time; + +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D + // If using rigid body for motion, then we need to increment + // our tick latency based on the number of times FixedUpdate + // is executed. + if (m_UseRigidbodyForMotion) + { + tickLatencyAsTime += m_FixedTimeFrameDelta; + } +#endif + // Smooth dampening and extrapolation specific: // We clamp between the tick rate frequency and the tick latency x tick rate frequency var minDeltaTime = timeSystem.FixedDeltaTimeAsDouble; @@ -3983,6 +4019,7 @@ private void UpdateInterpolation() { m_PositionInterpolator.MaximumInterpolationTime = PositionMaxInterpolationTime; } + m_PositionInterpolator.LerpSmoothEnabled = PositionLerpSmoothing; // If either of these two position interpolation related values have changed, then reset the current state being interpolated. if (m_PreviousPositionInterpolationType != PositionInterpolationType || m_PreviousPositionLerpSmoothing != PositionLerpSmoothing) @@ -3994,12 +4031,12 @@ private void UpdateInterpolation() if (PositionInterpolationType == InterpolationTypes.Lerp) { - m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime, PositionLerpSmoothing); + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); } else { m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, - PositionInterpolationType == InterpolationTypes.LerpExtrapolateBlend, PositionLerpSmoothing); + PositionInterpolationType == InterpolationTypes.LerpExtrapolateBlend); } } @@ -4010,6 +4047,8 @@ private void UpdateInterpolation() m_RotationInterpolator.MaximumInterpolationTime = RotationMaxInterpolationTime; } + m_RotationInterpolator.LerpSmoothEnabled = RotationLerpSmoothing; + // If either of these two rotation interpolation related values have changed, then reset the current state being interpolated. if (m_PreviousRotationInterpolationType != RotationInterpolationType || m_PreviousRotationLerpSmoothing != RotationLerpSmoothing) { @@ -4024,12 +4063,12 @@ private void UpdateInterpolation() // When using full precision Slerp towards the target rotation. /// m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; - m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime, RotationLerpSmoothing); + m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); } else { m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, - RotationInterpolationType == InterpolationTypes.LerpExtrapolateBlend, RotationLerpSmoothing); + RotationInterpolationType == InterpolationTypes.LerpExtrapolateBlend); } } @@ -4040,6 +4079,8 @@ private void UpdateInterpolation() m_ScaleInterpolator.MaximumInterpolationTime = ScaleMaxInterpolationTime; } + m_ScaleInterpolator.LerpSmoothEnabled = ScaleLerpSmoothing; + // If either of these two rotation interpolation related values have changed, then reset the current state being interpolated. if (m_PreviousScaleInterpolationType != ScaleInterpolationType || m_PreviousScaleLerpSmoothing != ScaleLerpSmoothing) { @@ -4050,27 +4091,61 @@ private void UpdateInterpolation() if (ScaleInterpolationType == InterpolationTypes.Lerp) { - m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime, ScaleLerpSmoothing); + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); } else { m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, - ScaleInterpolationType == InterpolationTypes.LerpExtrapolateBlend, ScaleLerpSmoothing); + ScaleInterpolationType == InterpolationTypes.LerpExtrapolateBlend); } } -#if DEBUG_LINEARBUFFER +#if DEBUG_LINEARBUFFER && UNITY_EDITOR + // For debugging purposes if (GatherStats) { + if (!m_PositionInterpolator.InterpolateState.Target.HasValue) + { + return; + } var posStats = new NTPositionStats() { + FrameCount = m_FrameCount, + FixedFrameCount = m_FixedFrameCount, + FixedUpdatesPerFrameCount = m_FixedUpdatesPerFrameCount, + TimeSynchCount = m_CachedNetworkManager.NetworkTimeSystem.SyncCount, Tick = timeSystem.Tick, Time = timeSystem.Time, TicksAgoTime = tickLatencyAsTime, BufferCount = m_PositionInterpolator.m_BufferQueue.Count, - CurrentState = m_PositionInterpolator.InterpolateState, + TickMeasured = (int)Math.Round(m_PositionInterpolator.InterpolateState.Target.Value.TimeSent / timeSystem.FixedDeltaTimeAsDouble, MidpointRounding.AwayFromZero), + LerpT = m_PositionInterpolator.InterpolateState.LerpT, + LerpTPredict = m_PositionInterpolator.InterpolateState.LerpTPredict, + DeltaTime = m_PositionInterpolator.InterpolateState.DeltaTime, + DeltaTimePredict = m_PositionInterpolator.InterpolateState.DeltaTimePredict, + MaxDeltaTime = m_PositionInterpolator.InterpolateState.MaxDeltaTime, + TimeSent = m_PositionInterpolator.InterpolateState.Target.Value.TimeSent, + TimeToTargetValue = m_PositionInterpolator.InterpolateState.TimeToTargetValue, + PreviousValue = m_PositionInterpolator.InterpolateState.PreviousValue, + PredictValue = m_PositionInterpolator.InterpolateState.PredictValue, + CurrentValue = m_PositionInterpolator.InterpolateState.CurrentValue, + TargetValue = m_PositionInterpolator.InterpolateState.Target.Value.Item, + Buffer = new List(), }; - PositionStats.Add(posStats); + + foreach (var entry in m_PositionInterpolator.m_BufferQueue) + { + posStats.Buffer.Add(new BufferEntry() + { + TimeSent = entry.TimeSent, + Position = entry.Item, + }); + } + if (!PositionStats.ContainsKey(posStats.TickMeasured)) + { + PositionStats.Add(posStats.TickMeasured, new List()); + } + PositionStats[posStats.TickMeasured].Add(posStats); } #endif } @@ -4103,6 +4178,42 @@ public virtual void OnUpdate() } #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D +#if DEBUG_LINEARBUFFER && UNITY_EDITOR + // For debugging purposes + private int m_FrameCount = 0; + private int m_FixedFrameCount = 0; + private int m_FixedUpdatesPerFrameCount = 0; +#endif + + // This is used during fixed update in case there are multiple fixed update passes without any frame pass. + private float m_FixedTimeFrameDelta; + + // The fixed time (static time step) value for the current frame (in the event it is changed at runtime). + private float m_DeltaFixedUpdateCached; + + /// + /// Resets the total fixed update time passed. + /// This handles dealing with multiple passes within FixedUpdate and interpolation. + /// + internal void ResetFixedTimeDelta() + { + // If not spawned or this instance has authority, exit early + if (!m_UseRigidbodyForMotion || !IsSpawned || CanCommitToTransform) + { + return; + } + + // Get the current fixed delta time (used in fixed upate) + m_DeltaFixedUpdateCached = m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime; + // Reset the total fixed update time (increased each time physics invokes FixedUpdate within the same frame) + m_FixedTimeFrameDelta = 0.0f; +#if DEBUG_LINEARBUFFER && UNITY_EDITOR + // For debugging purposes + m_FrameCount++; + m_FixedUpdatesPerFrameCount = 0; +#endif + } + /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . @@ -4117,6 +4228,12 @@ public virtual void OnFixedUpdate() m_NetworkRigidbodyInternal.WakeIfSleeping(); +#if DEBUG_LINEARBUFFER && UNITY_EDITOR + // For debugging purposes + m_FixedFrameCount++; + m_FixedUpdatesPerFrameCount++; +#endif + // Update interpolation when enabled if (Interpolate) { @@ -4125,6 +4242,10 @@ public virtual void OnFixedUpdate() // Apply the current authoritative state ApplyAuthoritativeState(); + + // Increment the time passed based on our current fixed update rate in case + // FixedUpdate is invoked more than once within a single frame. + m_FixedTimeFrameDelta += m_DeltaFixedUpdateCached; } #endif @@ -4279,11 +4400,15 @@ private void UpdateTransformState() #region NETWORK TICK REGISTRATOIN AND HANDLING private static Dictionary s_NetworkTickRegistration = new Dictionary(); /// - /// Adjusts the over-all tick offset (i.e. how many ticks ago) and how wide of a maximum delta time will be used for the various . + /// Adjusts the over-all tick offset (i.e. how many ticks ago) and how wide of a maximum delta time will be used for the + /// various . /// /// - /// Note: You can adjust this value during runtime. Increasing this value will set non-authority instances that much further behind the authority instance but - /// will increase the number of state updates to be processed. This can be useful under higher latency conditions. + /// Note: You can adjust this value during runtime. Increasing this value will set non-authority instances that much further + /// behind the authority instance but will increase the number of state updates to be processed. Increasing this can be useful + /// under higher latency conditions.
+ /// The default value is 1 tick (plus the tick latency). When running on a local network, reducing this to 0 is recommended.
+ /// ///
public static int InterpolationBufferTickOffset = 0; internal static float GetTickLatency(NetworkManager networkManager) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index d564f0f8f3..22e9f74413 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -333,6 +333,25 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) MessageManager.CleanupDisconnectedClients(); AnticipationSystem.ProcessReanticipation(); +#if COM_UNITY_MODULES_PHYSICS + foreach (var networkObjectEntry in NetworkTransformFixedUpdate) + { + // if not active or not spawned then skip + if (!networkObjectEntry.Value.gameObject.activeInHierarchy || !networkObjectEntry.Value.IsSpawned) + { + continue; + } + + foreach (var networkTransformEntry in networkObjectEntry.Value.NetworkTransforms) + { + // only update if enabled + if (networkTransformEntry.enabled) + { + networkTransformEntry.ResetFixedTimeDelta(); + } + } + } +#endif } break; #if COM_UNITY_MODULES_PHYSICS diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs index 97b0fcc5e7..e3ab1dfe32 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/TimeSyncMessage.cs @@ -26,6 +26,7 @@ public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, Tick); + networkManager.NetworkTimeSystem.SyncCount++; networkManager.NetworkTimeSystem.Sync(time.Time, networkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(context.SenderId) / 1000d); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index 54c7736b58..652b59cc09 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -82,8 +82,14 @@ public class NetworkTimeSystem /// The averaged latency in network ticks between a client and server. ///
/// - /// For a distributed authority network topology, this latency is between - /// the client and the distributed authority service instance. + /// For a distributed authority network topology, this latency is between the client and the + /// distributed authority service instance.
+ /// Note: uses this value plus an additional global + /// offset when interpolation + /// is enabled.
+ /// To see the current tick latency:
+ /// -
+ /// -
///
public int TickLatency = 1; @@ -257,7 +263,7 @@ public void Reset(double serverTimeSec, double rttSec) Sync(serverTimeSec, rttSec); Advance(0); } - + internal int SyncCount; /// /// Synchronizes the time system with up-to-date network statistics but does not change any time values or advance the time. /// From 2e5e62ff755947d1d5ca52d645d62dc95025ebb7 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 25 Mar 2025 19:20:26 -0500 Subject: [PATCH 26/50] style removing the lerpSmoothing parameter from xml API documentation. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 9005efec39..14342cc8a5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -537,7 +537,6 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) /// time since last call /// our current time /// current server time - /// Determines if lerp smoothing is enabled for this instance. /// The newly interpolated value of type 'T' public T Update(float deltaTime, double renderTime, double serverTime) { From 1175088989ef644b8dd10532923012390d0fd878 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 25 Mar 2025 19:21:24 -0500 Subject: [PATCH 27/50] style One more removal of the same lerpSmoothing parameter in comments. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 14342cc8a5..116dd88f4f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -413,7 +413,6 @@ internal void ResetCurrentState() /// The minimum time delta between the current and target value. /// The maximum time delta between the current and target value. /// Determines whether to use smooth dampening or extrapolation. - /// Determines if lerp smoothing is enabled for this instance. /// The newly interpolated value of type 'T' internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime, double maxDeltaTime, bool isLerpAndExtrapolate) { From 139ce32b5bdebf81ef135c6f0373f341c197782d Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 26 Mar 2025 16:32:19 -0500 Subject: [PATCH 28/50] style updating xml api documentation to reflect the changes made. --- .../Runtime/Components/NetworkTransform.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 0462ccc6d0..07aaed9475 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -960,12 +960,14 @@ public enum InterpolationTypes Lerp, /// /// Lerp, Extrapolate, and Blend - /// Uses a 3 to 4 phase lerp towards the target, extrapolate towards the target, blend the two results, and (optionally) smooth the final value.
+ /// Uses a 3 to 5 phase lerp towards the target, extrapolate towards the target, blend the two results, and (optionally) smooth the final value.
/// - The first phase lerps towards the current tick state update being processed.
/// - The second phase lerps unclamped (extrapolates) towards the current tick state update and will extrapolate this value up to a calculated maximum delta time. /// The maximum delta time is the tick latency, calculated from an estimated RTT each time the network time is updated, plus the . The sum is multiplied by the tick frequency (one over tick rate).
/// - The third phase lerps between the results of the first and second phases by the current delta time.
- /// - The fourth phase (optional) performs a lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. + /// - The fourth and fifth phase (optional) performs: + /// -- A 1/3rd lerp towards the target from the current respective transform value. + /// -- A lerp smoothing where the result of the 1/3rd lerp operation is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. ///
/// /// Note: This is slightly more computationally expensive than the approach.
@@ -980,11 +982,13 @@ public enum InterpolationTypes ///
LerpExtrapolateBlend, /// - /// Uses a 3 to 4 phase smooth dampen towards the target, smooth dampen towards the next target, blend the two results, and (optionally) smooth the final value.
+ /// Uses a 3 to 5 phase smooth dampen towards the target, smooth dampen towards the next target, blend the two results, and (optionally) smooth the final value.
/// - The first phase smooth dampens towards the current tick state update being processed.
/// - The second phase smooth dampens towards the next tick state's target. If there is no next tick state update, then the target predicted value is the current state target that smooth dampens 1 frame (average delta time) ahead.
/// - The third phase lerps between the results of the first and second phases by the current delta time.
- /// - The fourth phase (optional) performs a lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. + /// - The fourth and fifth phase (optional) performs: + /// -- A 1/3rd lerp towards the target from the current respective transform value. + /// -- A lerp smoothing where the result of the 1/3rd lerp operation is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. ///
/// /// Note: Smooth dampening is computationally more expensive than the and approaches.
From 049b1c3c97518bff2c8eb8c80e5e4e908253cc3f Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 26 Mar 2025 16:32:30 -0500 Subject: [PATCH 29/50] update adding changelog entries. --- com.unity.netcode.gameobjects/CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9996ef2d61..e882cb6ea8 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added `LerpExtrapolateBlend` interpolation type that provides users with something between standard lerp and smooth dampening. (#3355) +- Added property to enable or disable lerp smoothing for position, rotation, and scale interpolators. (#3355) +- Added `NetworkTransform.InterpolationBufferTickOffset` static property to provide users with a way to increase or decrease the time marker where interpolators will pull state update from the queue. (#3355) - Added interpolator types as an inspector view selection for position, rotation, and scale. (#3337) - Added a new smooth dampening interpolator type that provides a nice balance between precision and smoothing results. (#3337) - Added `NetworkTimeSystem.TickLatency` property that provides the average latency of a client. (#3337) @@ -18,6 +21,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where the time delta that interpolators used would not be properly updated during multiple fixed update invocations within the same player loop frame. (#3355) - Fixed issue when using a distributed authority network topology and many clients attempt to connect simultaneously the session owner could max-out the maximum in-flight reliable messages allowed, start dropping packets, and some of the connecting clients would fail to fully synchronize. (#3350) - Fixed issue when using a distributed authority network topology and scene management was disabled clients would not be able to spawn any new network prefab instances until synchronization was complete. (#3350) - Fixed issue where an owner that changes ownership, when using a distributed authority network topology, could yield identical previous and current owner identifiers. This could also cause `NetworkTransform` to fail to change ownership which would leave the previous owner still subscribed to network tick events. (#3347) @@ -39,7 +43,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed - Changed `BufferedLinearInterpolator.Update(float deltaTime, NetworkTime serverTime)` as being deprecated since this method is only used for internal testing purposes. (#3337) -- Ensured that a useful error is thrown when attempting to build a dedicated server with Unity Transport that uses websockets. (#3336) +- Changed error thrown when attempting to build a dedicated server with Unity Transport that uses websockets to provide more useful information to the user. (#3336) - Changed root in-scene placed `NetworkObject` instances now will always have either the `Distributable` permission set unless the `SessionOwner` permission is set. (#3305) - Changed the `DestroyObject` message to reduce the serialized message size and remove the unnecessary message field. (#3304) - Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) From bab3abcc37d11baa67e1f2f15e2bc6be1aa7c612 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 27 Mar 2025 19:32:20 -0500 Subject: [PATCH 30/50] update Some adjustments to handle coming to a stop and to no longer clamp the timespan between state updates for LerpExtrapolateBlend and SmoothDampening. Adding fixed delta to server time (if it is of any value then multiple fixed updates have run within a single frame). Updated XML API documentation (again). --- .../BufferedLinearInterpolator.cs | 47 ++- .../Runtime/Components/NetworkTransform.cs | 322 ++++++++++++------ 2 files changed, 254 insertions(+), 115 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 116dd88f4f..62011f9da8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -312,6 +312,17 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double var currentTargetTimeReached = false; var dequeuedCount = 0; + // In the event there is nothing left in the queue (i.e. motion/change stopped), we still need to determine if the target has been reached. + if (!noStateSet && m_BufferQueue.Count == 0) + { + if (!InterpolateState.TargetReached) + { + InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); + } + return; + } + + // Continue to process any remaining state updates in the queue (if any) while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) { // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing @@ -325,7 +336,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double { potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent >= InterpolateState.Target.Value.TimeSent; currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing ? 1.0f : 0.85f) || InterpolateState.TargetReached; - if (!potentialItemNeedsProcessing && !InterpolateState.TargetReached) + if (!InterpolateState.TargetReached) { InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); } @@ -366,14 +377,19 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double if (isPredictedLerp) { InterpolateState.Phase1Value = InterpolateState.PreviousValue; - InterpolateState.Phase2Value = Interpolate(InterpolateState.PredictValue, target.Item, InterpolateState.AverageDeltaTime); + InterpolateState.Phase2Value = Interpolate(InterpolateState.PreviousValue, target.Item, InterpolateState.AverageDeltaTime); + } + else + { + InterpolateState.PredictValue = InterpolateState.PreviousValue; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; } InterpolateState.MaxDeltaTime = maxDeltaTime; InterpolateState.PredictingNext = m_BufferQueue.Count > 0; } - // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated - // for each item as opposed to losing the resolution of the values. - InterpolateState.SetTimeToTarget(Math.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime * dequeuedCount)); + // We continue to stretch the time out if we are far behind in processing the buffer. + // TODO: We need to compress time when there is a large amount of items to be processed. + InterpolateState.SetTimeToTarget(Math.Max((float)(target.TimeSent - startTime), minDeltaTime)); InterpolateState.Target = target; } } @@ -446,10 +462,8 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime // If lerp smoothing is enabled, then smooth current value towards the target value if (LerpSmoothEnabled) { - // Progress by 1/3rd of the way towards the target in order to assure the target is reached sooner - var oneThirdPoint = Interpolate(InterpolateState.CurrentValue, targetValue, 0.3333f); // Apply the smooth lerp from the oneThirpoint to the target to help smooth the final value - InterpolateState.CurrentValue = Interpolate(oneThirdPoint, targetValue, deltaTime / MaximumInterpolationTime); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / MaximumInterpolationTime); } else { @@ -457,6 +471,20 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime InterpolateState.CurrentValue = targetValue; } } + else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. + if (m_BufferQueue.Count == 0) + { + // When the delta between the time sent and the current tick latency time-window is greater than the max delta time + // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), + // we will want to reset the interpolator with the current known value. This prevents the next received state update's + // time to be calculated against the last calculated time which if there is an extended period of time between the two + // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then + // starts moving again). + if ((tickLatencyAsTime - InterpolateState.Target.Value.TimeSent) > InterpolateState.MaxDeltaTime + minDeltaTime) + { + InterpolateState.Reset(InterpolateState.CurrentValue); + } + } } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; @@ -567,8 +595,7 @@ public T Update(float deltaTime, double renderTime, double serverTime) if (LerpSmoothEnabled) { // Assure our MaximumInterpolationTime is valid and that the second lerp time ranges between deltaTime and 1.0f. - var secondLerpTime = Mathf.Clamp(deltaTime / MaximumInterpolationTime, deltaTime, 1.0f); - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, secondLerpTime); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, deltaTime / MaximumInterpolationTime); } else { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 07aaed9475..a9847244e3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -935,6 +935,26 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #region PROPERTIES AND GENERAL METHODS + /// + /// Pertains to Owner Authority and Interpolation
+ /// When enabled (default), 1 additional tick is added to the total number of ticks used to calculate the tick latency ("ticks ago") as a time. + /// This calculated time value is passed into the respective and used to determine if any pending + /// state updates in the queue should be processed. + /// The additional tick value is only applied when: + /// + /// The is using a authority mode. + /// The non-authority instance is a client (i.e. not host or server). + /// The network topology being used is . + /// + ///
+ /// + /// When calculating the total tick latency as time value, the is added to the + /// and if this property is enabled (and the conditions above are met) an additional tick is added to the final resultant value.
+ /// Note: The reason behind this additional tick latency value is due to the 2 RTT timespan when a client state update is sent to the host or server (1 RTT) + /// and the host or server relays this state update to all non-authority instances (1 RTT). + ///
+ public bool AutoOwnerAuthorityTickOffset = true; + /// /// The different interpolation types used with to help smooth interpolation results. /// Interpolation types can be changed during runtime. @@ -943,63 +963,75 @@ public enum InterpolationTypes { /// /// Lerp (original NGO lerping model)
- /// Uses a 1 to 2 phase interpolation approach where:
- /// - The fist phase lerps from the previous state update value to the next state update value.
- /// - The second phase (optional) performs a lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of delta time divided by the respective max interpolation time. + /// Uses a 1 to 2 phase interpolation approach where:
+ /// + /// The first phase lerps from the previous state update value to the next state update value. + /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of delta time divided by the respective max interpolation time. + /// ///
/// /// Lowest computation cost of the three options.
/// For more information:
- /// -
- /// -
- /// -
- /// -
- /// -
- /// -
+ /// + /// + /// + /// + /// + /// + /// + /// ///
Lerp, /// /// Lerp, Extrapolate, and Blend - /// Uses a 3 to 5 phase lerp towards the target, extrapolate towards the target, blend the two results, and (optionally) smooth the final value.
- /// - The first phase lerps towards the current tick state update being processed.
- /// - The second phase lerps unclamped (extrapolates) towards the current tick state update and will extrapolate this value up to a calculated maximum delta time. - /// The maximum delta time is the tick latency, calculated from an estimated RTT each time the network time is updated, plus the . The sum is multiplied by the tick frequency (one over tick rate).
- /// - The third phase lerps between the results of the first and second phases by the current delta time.
- /// - The fourth and fifth phase (optional) performs: - /// -- A 1/3rd lerp towards the target from the current respective transform value. - /// -- A lerp smoothing where the result of the 1/3rd lerp operation is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. + /// Uses a 3 to 4 phase approach that lerps towards the target, extrapolate towards the target, blends the two results, and (optionally) smooth the final value.
+ /// + /// The first phase lerps towards the current tick state update being processed. + /// The second phase lerps unclamped (extrapolates) towards the current tick state update and will extrapolate this value up to a calculated maximum state update delta time. + /// + /// + /// The maximum state update time is based on plus the . The sum is multiplied by the tick frequency (one over tick rate). + /// + /// + /// The third phase lerps between the results of the first and second phases by the current delta time. + /// The fourth phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. + /// ///
/// /// Note: This is slightly more computationally expensive than the approach.
/// It is recommended to turn lerp smoothing off or adjust the interpolation time to a lower value if you want a more precise end result.
/// For more information:
- /// -
- /// -
- /// -
- /// -
- /// -
- /// -
+ /// + /// + /// + /// + /// + /// + /// + /// ///
LerpExtrapolateBlend, /// - /// Uses a 3 to 5 phase smooth dampen towards the target, smooth dampen towards the next target, blend the two results, and (optionally) smooth the final value.
- /// - The first phase smooth dampens towards the current tick state update being processed.
- /// - The second phase smooth dampens towards the next tick state's target. If there is no next tick state update, then the target predicted value is the current state target that smooth dampens 1 frame (average delta time) ahead.
- /// - The third phase lerps between the results of the first and second phases by the current delta time.
- /// - The fourth and fifth phase (optional) performs: - /// -- A 1/3rd lerp towards the target from the current respective transform value. - /// -- A lerp smoothing where the result of the 1/3rd lerp operation is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. + /// Uses a 3 to 4 phase approach that smooth dampens towards the target, smooth dampens ahead of the target, blends the two results, and (optionally) smooth the final value.
+ /// + /// The first phase smooth dampens towards the current tick state update being processed by the accumulated delta time relative to the time to target. + /// The second phase smooth dampens ahead of the previous phase by the accumulated delta time, relative to the time to target, plus the current delta time for the current update. + /// The third phase lerps between the results of the first and second phases by the current delta time. + /// The fourth phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. + /// ///
/// /// Note: Smooth dampening is computationally more expensive than the and approaches.
/// It is recommended to turn lerp smoothing off or adjust the interpolation time to a lower value if you want a more precise end result. /// For more information:
- /// -
- /// -
- /// -
- /// -
- /// -
- /// -
+ /// + /// + /// + /// + /// + /// + /// + /// ///
SmoothDampening } @@ -1008,10 +1040,15 @@ public enum InterpolationTypes /// The position interpolation type to use for the instance. ///
/// - /// - yields a traditional linear result.
- /// - adjusts based on the rate of change.
- /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
- /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
+ /// + /// Yields a traditional linear result. + /// Lerps, extrapolates, and blends the results. + /// Adjusts based on the rate of change. + /// Things to consider:
+ /// + /// You can have mixed interpolation types between position, rotation, and scale on the same instance. + /// You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion. + /// ///
[Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes PositionInterpolationType; @@ -1021,10 +1058,15 @@ public enum InterpolationTypes /// The rotation interpolation type to use for the instance. ///
/// - /// - yields a traditional linear result.
- /// - adjusts based on the rate of change.
- /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
- /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
+ /// + /// Yields a traditional linear result. + /// Lerps, extrapolates, and blends the results. + /// Adjusts based on the rate of change. + /// Things to consider:
+ /// + /// You can have mixed interpolation types between position, rotation, and scale on the same instance. + /// You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion. + /// ///
[Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes RotationInterpolationType; @@ -1034,17 +1076,22 @@ public enum InterpolationTypes /// The scale interpolation type to use for the instance. ///
/// - /// - yields a traditional linear result.
- /// - adjusts based on the rate of change.
- /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
- /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
+ /// + /// Yields a traditional linear result. + /// Lerps, extrapolates, and blends the results. + /// Adjusts based on the rate of change. + /// Things to consider:
+ /// + /// You can have mixed interpolation types between position, rotation, and scale on the same instance. + /// You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion. + /// ///
[Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes ScaleInterpolationType; private InterpolationTypes m_PreviousScaleInterpolationType; /// - /// Controls position interpolaiton smoothing. + /// Controls position interpolation smoothing. /// /// /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . @@ -1053,21 +1100,25 @@ public enum InterpolationTypes private bool m_PreviousPositionLerpSmoothing; /// - /// The position interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. - /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
- /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
- /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). + /// The position interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp.
+ /// + /// The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). + /// The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value). + /// This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. linear velocity or the like). + /// ///
/// - /// - Only used When is enabled and using .
- /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + /// + /// Only used When is enabled. + /// The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + /// ///
[Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] public float PositionMaxInterpolationTime = 0.1f; /// - /// Controls rotation interpolaiton smoothing. + /// Controls rotation interpolation smoothing. /// /// /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . @@ -1077,20 +1128,24 @@ public enum InterpolationTypes /// /// The rotation interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. - /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
- /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
- /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). + /// + /// The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). + /// The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value). + /// This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. angular velocity). + /// ///
/// - /// - Only used When is enabled and using .
- /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + /// + /// Only used When is enabled. + /// The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + /// ///
[Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] public float RotationMaxInterpolationTime = 0.1f; /// - /// Controls scale interpolaiton smoothing. + /// Controls scale interpolation smoothing. /// /// /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . @@ -1100,13 +1155,17 @@ public enum InterpolationTypes /// /// The scale interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. - /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
- /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
- /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). + /// + /// The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). + /// The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value). + /// This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value. + /// ///
/// - /// - Only used When is enabled and using .
- /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + /// + /// Only used When is enabled. + /// The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + /// ///
[Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] @@ -1118,7 +1177,7 @@ public enum InterpolationTypes public enum AuthorityModes { /// - /// Server pushes transform state updates + /// Server pushes transform state updates. /// Server, /// @@ -1145,27 +1204,29 @@ public enum AuthorityModes /// This can help to reduce out of sync updates that can lead to slight jitter between a parent and its child/children. /// /// - /// - If this is set on a child and the parent does not have this set then the child will not be tick synchronized with its parent.
- /// - If the parent instance does not send any state updates, the children will still send state updates when exceeding axis delta threshold.
- /// - This does not need to be set on children to be applied. + /// + /// If this is set on a child and the parent does not have this set then the child will not be tick synchronized with its parent. + /// If the parent instance does not send any state updates, the children will still send state updates when exceeding axis delta threshold. + /// This does not need to be set on children to be applied. + /// ///
[Tooltip("When enabled, any parented children of this instance will send a state update when this instance sends a state update. If this instance doesn't send a state update, the children will still send state updates when reaching their axis specified threshold delta. Children do not have to have this setting enabled.")] public bool TickSyncChildren = false; /// - /// The default position change threshold value. + /// The default position change threshold value.
/// Any changes above this threshold will be replicated. ///
public const float PositionThresholdDefault = 0.001f; /// - /// The default rotation angle change threshold value. + /// The default rotation angle change threshold value.
/// Any changes above this threshold will be replicated. ///
public const float RotAngleThresholdDefault = 0.01f; /// - /// The default scale change threshold value. + /// The default scale change threshold value.
/// Any changes above this threshold will be replicated. ///
public const float ScaleThresholdDefault = 0.01f; @@ -1197,12 +1258,13 @@ public enum AuthorityModes /// are sent using a reliable fragmented sequenced network delivery. ///
/// - /// The following more critical state updates are still sent as reliable fragmented sequenced: - /// - The initial synchronization state update - /// - The teleporting state update. - /// - When using half float precision and the `NetworkDeltaPosition` delta exceeds the maximum delta forcing the axis in - /// question to be collapsed into the core base position, this state update will be sent as reliable fragmented sequenced. - /// + /// The following more critical state updates are still sent as reliable fragmented sequenced:
+ /// + /// The initial synchronization state update. + /// The teleporting state update. + /// When using half float precision and the `NetworkDeltaPosition` delta exceeds the maximum delta forcing the axis in + /// question to be collapsed into the core base position, this state update will be sent as reliable fragmented sequenced. + /// /// In order to preserve a continual consistency of axial values when unreliable delta messaging is enabled (due to the /// possibility of dropping packets), NetworkTransform instances will send 1 axial frame synchronization update per /// second (only for the axis marked to synchronize are sent as reliable fragmented sequenced) as long as a delta state @@ -1324,8 +1386,16 @@ internal bool SynchronizeScale /// The rotation threshold value that triggers a delta state update by the authoritative instance. ///
/// - /// Minimum Value: 0.00001 - /// Maximum Value: 360.0 + /// + /// + /// Minimum Value + /// 0.00001 + /// + /// + /// Maximum Value + /// 360.0 + /// + /// /// [Range(0.00001f, 360.0f)] public float RotAngleThreshold = RotAngleThresholdDefault; @@ -1339,7 +1409,7 @@ internal bool SynchronizeScale public float ScaleThreshold = ScaleThresholdDefault; /// - /// Enable this on the authority side for quaternion synchronization + /// Enable this on the authority side for quaternion synchronization. /// /// /// This is synchronized by authority. During runtime, this should only be changed by the @@ -1355,7 +1425,7 @@ internal bool SynchronizeScale /// /// This has a lower precision than half float precision. Recommended only for low precision /// scenarios. provides better precision at roughly half - /// the cost of a full quaternion update. + /// the cost of a full quaternion update.
/// This is synchronized by authority. During runtime, this should only be changed by the /// authoritative side. Non-authoritative instances will be overridden by the next /// authoritative state update. @@ -1364,7 +1434,7 @@ internal bool SynchronizeScale public bool UseQuaternionCompression = false; /// - /// Enable this to use half float precision for position, rotation, and scale. + /// Enable this to use half float precision for position, rotation, and scale.
/// When enabled, delta position synchronization is used. ///
/// @@ -1449,20 +1519,44 @@ internal bool SynchronizeScale /// Helper method that returns the space relative position of the transform. ///
/// - /// If InLocalSpace is then it returns the transform.localPosition - /// If InLocalSpace is then it returns the transform.position - /// When invoked on the non-authority side: - /// If is true then it will return the most + /// + /// If InLocalSpace is then it returns the transform.localPosition. + /// If InLocalSpace is then it returns the transform.position. + /// + /// + /// + /// When invoked on the non-authority side: + /// If is true then it will return the most /// current authority position from the most recent state update. This can be useful /// if interpolation is enabled and you need to determine the final target position. - /// When invoked on the authority side: - /// It will always return the space relative position. + /// + /// + /// When invoked on the authority side: + /// It will always return the space relative position. + /// /// /// - /// Authority always returns the space relative transform position (whether true or false). - /// Non-authority: - /// When false (default): returns the space relative transform position - /// When true: returns the authority position from the most recent state update. + /// + /// + /// Authority + /// Always returns the space relative transform position (whether true or false). + /// + /// + /// Non-authority: + /// + /// + /// When false(default) + /// Returns the space relative transform position. + /// + /// + /// + /// When true + /// + /// Returns the authority position from the most recent state update. + /// + /// + /// + /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1491,20 +1585,37 @@ public Vector3 GetSpaceRelativePosition(bool getCurrentState = false) /// Helper method that returns the space relative rotation of the transform. ///
/// - /// If InLocalSpace is then it returns the transform.localRotation - /// If InLocalSpace is then it returns the transform.rotation - /// When invoked on the non-authority side: + /// If InLocalSpace is then it returns the transform.localRotation.
+ /// If InLocalSpace is then it returns the transform.rotation.
+ /// When invoked on the non-authority side:
/// If is true then it will return the most /// current authority rotation from the most recent state update. This can be useful - /// if interpolation is enabled and you need to determine the final target rotation. - /// When invoked on the authority side: - /// It will always return the space relative rotation. + /// if interpolation is enabled and you need to determine the final target rotation.
+ /// When invoked on the authority side:
+ /// It will always return the space relative rotation.
///
/// - /// Authority always returns the space relative transform rotation (whether true or false). - /// Non-authority: - /// When false (default): returns the space relative transform rotation - /// When true: returns the authority rotation from the most recent state update. + /// + /// + /// Authority + /// Always returns the space relative transform rotation (whether true or false). + /// + /// + /// Non-authority: + /// + /// + /// When false(default) + /// Returns the space relative transform rotation. + /// + /// + /// + /// When true + /// + /// Returns the authority rotation from the most recent state update. + /// + /// + /// + /// /// /// public Quaternion GetSpaceRelativeRotation(bool getCurrentState = false) @@ -4004,6 +4115,7 @@ private void UpdateInterpolation() if (m_UseRigidbodyForMotion) { tickLatencyAsTime += m_FixedTimeFrameDelta; + currentTime += m_FixedTimeFrameDelta; } #endif From 1f549053b2681ce68331272917a0089c6100fafa Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 27 Mar 2025 19:46:15 -0500 Subject: [PATCH 31/50] style XML API stuff. --- .../Runtime/Components/NetworkTransform.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index a9847244e3..aebacafc7f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1041,9 +1041,9 @@ public enum InterpolationTypes ///
/// /// - /// Yields a traditional linear result. - /// Lerps, extrapolates, and blends the results. - /// Adjusts based on the rate of change. + /// Yields a traditional linear result. + /// Lerps, extrapolates, and blends the results. + /// Adjusts based on the rate of change. /// Things to consider:
/// /// You can have mixed interpolation types between position, rotation, and scale on the same instance. @@ -1059,9 +1059,9 @@ public enum InterpolationTypes /// /// /// - /// Yields a traditional linear result. - /// Lerps, extrapolates, and blends the results. - /// Adjusts based on the rate of change. + /// Yields a traditional linear result. + /// Lerps, extrapolates, and blends the results. + /// Adjusts based on the rate of change. /// Things to consider:
/// /// You can have mixed interpolation types between position, rotation, and scale on the same instance. @@ -1534,6 +1534,7 @@ internal bool SynchronizeScale /// When invoked on the authority side: /// It will always return the space relative position. ///
+ ///
///
/// /// @@ -1616,6 +1617,7 @@ public Vector3 GetSpaceRelativePosition(bool getCurrentState = false) ///
///
/// + /// /// /// public Quaternion GetSpaceRelativeRotation(bool getCurrentState = false) From 0959618513a89ac30391c502a92a3466f51aa8d0 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 27 Mar 2025 19:56:12 -0500 Subject: [PATCH 32/50] style arrg... lists! PVP! --- .../Runtime/Components/NetworkTransform.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index aebacafc7f..d9c4630b5a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -988,11 +988,6 @@ public enum InterpolationTypes /// /// The first phase lerps towards the current tick state update being processed. /// The second phase lerps unclamped (extrapolates) towards the current tick state update and will extrapolate this value up to a calculated maximum state update delta time. - /// - /// - /// The maximum state update time is based on plus the . The sum is multiplied by the tick frequency (one over tick rate). - /// - /// /// The third phase lerps between the results of the first and second phases by the current delta time. /// The fourth phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. /// @@ -1044,6 +1039,7 @@ public enum InterpolationTypes /// Yields a traditional linear result. /// Lerps, extrapolates, and blends the results. /// Adjusts based on the rate of change. + /// /// Things to consider:
/// /// You can have mixed interpolation types between position, rotation, and scale on the same instance. @@ -1062,6 +1058,7 @@ public enum InterpolationTypes /// Yields a traditional linear result. /// Lerps, extrapolates, and blends the results. /// Adjusts based on the rate of change. + /// /// Things to consider:
/// /// You can have mixed interpolation types between position, rotation, and scale on the same instance. @@ -1080,6 +1077,7 @@ public enum InterpolationTypes /// Yields a traditional linear result. /// Lerps, extrapolates, and blends the results. /// Adjusts based on the rate of change. + /// /// Things to consider:
/// /// You can have mixed interpolation types between position, rotation, and scale on the same instance. @@ -1558,6 +1556,7 @@ internal bool SynchronizeScale /// /// /// + /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] From bcadba7dc47502f76bb3df546e31f44836f0a91b Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 27 Mar 2025 20:11:10 -0500 Subject: [PATCH 33/50] style 2 more...evidently you can't use within a --- .../Runtime/Components/NetworkTransform.cs | 82 ++++++------------- 1 file changed, 27 insertions(+), 55 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index d9c4630b5a..d63adf54c1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1535,28 +1535,10 @@ internal bool SynchronizeScale /// ///
/// - /// - /// - /// Authority - /// Always returns the space relative transform position (whether true or false). - /// - /// - /// Non-authority: - /// - /// - /// When false(default) - /// Returns the space relative transform position. - /// - /// - /// - /// When true - /// - /// Returns the authority position from the most recent state update. - /// - /// - /// - /// - /// + /// Authority always returns the space relative transform position (whether true or false).
+ /// Non-authority:
+ /// When false (default): returns the space relative transform position.
+ /// When true: returns the authority position from the most recent state update. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1585,38 +1567,28 @@ public Vector3 GetSpaceRelativePosition(bool getCurrentState = false) /// Helper method that returns the space relative rotation of the transform. /// /// - /// If InLocalSpace is then it returns the transform.localRotation.
- /// If InLocalSpace is then it returns the transform.rotation.
- /// When invoked on the non-authority side:
- /// If is true then it will return the most - /// current authority rotation from the most recent state update. This can be useful - /// if interpolation is enabled and you need to determine the final target rotation.
- /// When invoked on the authority side:
- /// It will always return the space relative rotation.
- ///
- /// + /// + /// If InLocalSpace is then it returns the transform.localRotation. + /// If InLocalSpace is then it returns the transform.rotation. + /// /// /// - /// Authority - /// Always returns the space relative transform rotation (whether true or false). + /// When invoked on the non-authority side: + /// If is true then it will return the most + /// current authority position from the most recent state update. This can be useful + /// if interpolation is enabled and you need to determine the final target rotation. /// /// - /// Non-authority: - /// - /// - /// When false(default) - /// Returns the space relative transform rotation. - /// - /// - /// - /// When true - /// - /// Returns the authority rotation from the most recent state update. - /// - /// - /// - /// + /// When invoked on the authority side: + /// It will always return the space relative rotation. + /// /// + /// + /// + /// Authority always returns the space relative transform rotation (whether true or false).
+ /// Non-authority:
+ /// When false (default): returns the space relative transform rotation.
+ /// When true: returns the authority rotation from the most recent state update. /// /// public Quaternion GetSpaceRelativeRotation(bool getCurrentState = false) @@ -1635,17 +1607,17 @@ public Quaternion GetSpaceRelativeRotation(bool getCurrentState = false) /// Helper method that returns the scale of the transform. /// /// - /// When invoked on the non-authority side: + /// When invoked on the non-authority side:
/// If is true then it will return the most /// current authority scale from the most recent state update. This can be useful - /// if interpolation is enabled and you need to determine the final target scale. - /// When invoked on the authority side: + /// if interpolation is enabled and you need to determine the final target scale.
+ /// When invoked on the authority side:
/// It will always return the space relative scale. ///
/// - /// Authority always returns the space relative transform scale (whether true or false). - /// Non-authority: - /// When false (default): returns the space relative transform scale + /// Authority always returns the space relative transform scale (whether true or false).
+ /// Non-authority:
+ /// When false (default): returns the space relative transform scale.
/// When true: returns the authority scale from the most recent state update. /// /// From f6ad63f57925414783b5b2204d8085816e42171f Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 27 Mar 2025 21:23:20 -0500 Subject: [PATCH 34/50] fix Quaternion approximation calculation was wrong. --- .../Interpolator/BufferedLinearInterpolatorQuaternion.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs index 8eb6d29bcc..82dc16640a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs @@ -63,7 +63,10 @@ private protected override Quaternion SmoothDamp(Quaternion current, Quaternion /// private protected override bool IsAproximately(Quaternion first, Quaternion second, float precision) { - return (1.0f - Quaternion.Dot(first, second)) <= precision; + return Mathf.Abs(first.x - second.x) <= precision && + Mathf.Abs(first.y - second.y) <= precision && + Mathf.Abs(first.z - second.z) <= precision && + Mathf.Abs(first.w - second.w) <= precision; } /// From 9e9172f989a471716a862d5e2814cdcf9cc2ada9 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 27 Mar 2025 21:29:27 -0500 Subject: [PATCH 35/50] update Adding [MethodImpl(MethodImplOptions.AggressiveInlining)] to float, Vector3, and Quaternion LinearBufferInterpolator methods. --- .../Interpolator/BufferedLinearInterpolatorFloat.cs | 5 +++++ .../Interpolator/BufferedLinearInterpolatorQuaternion.cs | 6 ++++++ .../Interpolator/BufferedLinearInterpolatorVector3.cs | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs index 9d3765fb40..b1a0b98474 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode @@ -9,24 +10,28 @@ namespace Unity.Netcode public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator { /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override float InterpolateUnclamped(float start, float end, float time) { return Mathf.LerpUnclamped(start, end, time); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override float Interpolate(float start, float end, float time) { return Mathf.Lerp(start, end, time); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override bool IsAproximately(float first, float second, float precision = 1E-07F) { return Mathf.Approximately(first, second); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override float SmoothDamp(float current, float target, ref float rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) { if (m_IsAngularValue) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs index 82dc16640a..5b8033a977 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode @@ -21,6 +22,7 @@ public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time) { if (IsSlerp) @@ -34,6 +36,7 @@ protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time) { if (IsSlerp) @@ -47,6 +50,7 @@ protected override Quaternion Interpolate(Quaternion start, Quaternion end, floa } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override Quaternion SmoothDamp(Quaternion current, Quaternion target, ref Quaternion rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) { Vector3 currentEuler = current.eulerAngles; @@ -61,6 +65,7 @@ private protected override Quaternion SmoothDamp(Quaternion current, Quaternion } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override bool IsAproximately(Quaternion first, Quaternion second, float precision) { return Mathf.Abs(first.x - second.x) <= precision && @@ -70,6 +75,7 @@ private protected override bool IsAproximately(Quaternion first, Quaternion seco } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected internal override Quaternion OnConvertTransformSpace(Transform transform, Quaternion rotation, bool inLocalSpace) { if (inLocalSpace) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs index 2bb32d5402..4bb7a066a8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode @@ -13,6 +14,7 @@ public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator public bool IsSlerp; /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, float time) { if (IsSlerp) @@ -26,6 +28,7 @@ protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, floa } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time) { if (IsSlerp) @@ -39,6 +42,7 @@ protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time) } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected internal override Vector3 OnConvertTransformSpace(Transform transform, Vector3 position, bool inLocalSpace) { if (inLocalSpace) @@ -53,12 +57,14 @@ protected internal override Vector3 OnConvertTransformSpace(Transform transform, } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override bool IsAproximately(Vector3 first, Vector3 second, float precision = 0.0001F) { return Vector3.Distance(first, second) <= precision; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 rateOfChange, float duration, float deltaTime, float maxSpeed) { if (m_IsAngularValue) From 283e2e8cc7848babab1cb6047bc1d156da70c8a9 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 28 Mar 2025 11:23:48 -0500 Subject: [PATCH 36/50] fix Updating the approximation approach to yield a more precise end result. --- .../Interpolator/BufferedLinearInterpolatorVector3.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs index 4bb7a066a8..e5d66b73cd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.CompilerServices; using UnityEngine; @@ -60,7 +61,9 @@ protected internal override Vector3 OnConvertTransformSpace(Transform transform, [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override bool IsAproximately(Vector3 first, Vector3 second, float precision = 0.0001F) { - return Vector3.Distance(first, second) <= precision; + return Math.Round(Mathf.Abs(first.x - second.x), 2) <= precision && + Math.Round(Mathf.Abs(first.y - second.y), 2) <= precision && + Math.Round(Mathf.Abs(first.z - second.z), 2) <= precision; } /// From 82306c0ae3bd69c411c745ca2d2d298494e9ab42 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 28 Mar 2025 11:35:38 -0500 Subject: [PATCH 37/50] fix Don't try to convert InterpolateState.Target if not assigned yet. --- .../Interpolator/BufferedLinearInterpolator.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 62011f9da8..59b5a94b14 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -754,9 +754,12 @@ internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) m_BufferQueue.Enqueue(entry); } InterpolateState.CurrentValue = OnConvertTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); - var end = InterpolateState.Target.Value; - end.Item = OnConvertTransformSpace(transform, end.Item, inLocalSpace); - InterpolateState.Target = end; + if (InterpolateState.Target.HasValue) + { + var end = InterpolateState.Target.Value; + end.Item = OnConvertTransformSpace(transform, end.Item, inLocalSpace); + InterpolateState.Target = end; + } InLocalSpace = inLocalSpace; } } From 643329aecc2f5468050b4b5b79c299a221656194 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 28 Mar 2025 13:55:53 -0500 Subject: [PATCH 38/50] update Making smooth lerp a slightly faster for LEB and SD interpolation types. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 59b5a94b14..5518e85742 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -377,7 +377,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double if (isPredictedLerp) { InterpolateState.Phase1Value = InterpolateState.PreviousValue; - InterpolateState.Phase2Value = Interpolate(InterpolateState.PreviousValue, target.Item, InterpolateState.AverageDeltaTime); + InterpolateState.Phase2Value = Interpolate(InterpolateState.PredictValue, target.Item, InterpolateState.AverageDeltaTime); } else { @@ -462,8 +462,8 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime // If lerp smoothing is enabled, then smooth current value towards the target value if (LerpSmoothEnabled) { - // Apply the smooth lerp from the oneThirpoint to the target to help smooth the final value - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / MaximumInterpolationTime); + // Apply the smooth lerp to the target to help smooth the final value. + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / (MaximumInterpolationTime * 0.3333f)); } else { From 35b7fbeb087d0d3d06fdb1addb5df096f2f53099 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 28 Mar 2025 18:41:31 -0500 Subject: [PATCH 39/50] update Exposing internal access to position and rotation interpolators for future tests and tools. Noticed the slerp for full precision rotation was only being applied when using Lerp, now it is for all interpolation types. Making the time to approximately reach adjustment be the default when there is no pending item to be consumed and slightly sooner when there is. Removing the adjustment to the slerp smoothing as this should be a consistantly applied value for all interpolation types. --- .../Interpolator/BufferedLinearInterpolator.cs | 10 +++++----- .../Runtime/Components/NetworkTransform.cs | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 5518e85742..1e5ab5e62f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -335,7 +335,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double if (!noStateSet) { potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent >= InterpolateState.Target.Value.TimeSent; - currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing ? 1.0f : 0.85f) || InterpolateState.TargetReached; + currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing ? 1.15f : 1.0f) || InterpolateState.TargetReached; if (!InterpolateState.TargetReached) { InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); @@ -463,7 +463,7 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime if (LerpSmoothEnabled) { // Apply the smooth lerp to the target to help smooth the final value. - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / (MaximumInterpolationTime * 0.3333f)); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / MaximumInterpolationTime); } else { @@ -574,10 +574,10 @@ public T Update(float deltaTime, double renderTime, double serverTime) // The original BufferedLinearInterpolator lerping script to assure the Smooth Dampening updates do not impact // this specific behavior. float t = 1.0f; - double range = InterpolateState.EndTime - InterpolateState.StartTime; - if (range > k_SmallValue) + InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime; + if (InterpolateState.TimeToTargetValue > k_SmallValue) { - t = (float)((renderTime - InterpolateState.StartTime) / range); + t = (float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue); if (t < 0.0f) { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index d63adf54c1..64dd3fc7c5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -4045,6 +4045,15 @@ public void ClearStats() PositionStats.Clear(); } #endif + internal BufferedLinearInterpolatorVector3 GetPositionInterpolator() + { + return m_PositionInterpolator; + } + + internal BufferedLinearInterpolatorQuaternion GetRotationInterpolator() + { + return m_RotationInterpolator; + } // Non-Authority private void UpdateInterpolation() @@ -4145,13 +4154,12 @@ private void UpdateInterpolation() m_PreviousRotationLerpSmoothing = RotationLerpSmoothing; m_RotationInterpolator.ResetCurrentState(); } - + // When using half precision Lerp towards the target rotation. + // When using full precision Slerp towards the target rotation. + /// + m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; if (RotationInterpolationType == InterpolationTypes.Lerp) { - // When using half precision Lerp towards the target rotation. - // When using full precision Slerp towards the target rotation. - /// - m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); } else From 90d900f12a4da527ad0228dd3a8fc4336ad95596 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 28 Mar 2025 18:46:27 -0500 Subject: [PATCH 40/50] test Renaming NetworkTransformMultipleChangesOverTime to just MultipleChangesOverTime (no need for the NetworkTransform part as it is already part of the NetworkTransformTests). Turning off Lerp smoothing for MultipleChangesOverTime and added lengthy comment as to why. Fixed the initial check for the first state update in MultipleChangesOverTime due to a change in the Interpolation value. It was not properly checking both pushed and updated values and should only be checked when interpolation is turned off as the default is enabled and when running MultipleChangesOverTime for 3 axis it remains enabled (i.e. there will be no state update for that as nothing changed). --- .../NetworkTransform/NetworkTransformBase.cs | 32 +++++++++++++ .../NetworkTransform/NetworkTransformTests.cs | 45 ++++++++++++++++--- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index e3fe4f4ce3..487662ee1a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -782,6 +782,7 @@ internal class NetworkTransformTestComponent : NetworkTransform protected override void OnAuthorityPushTransformState(ref NetworkTransformState networkTransformState) { + Debug.Log($"[Auth]{name} State Pushed."); StatePushed = true; AuthorityLastSentState = networkTransformState; AuthorityPushedTransformState?.Invoke(ref networkTransformState); @@ -792,10 +793,41 @@ protected override void OnAuthorityPushTransformState(ref NetworkTransformState public bool StateUpdated { get; internal set; } protected override void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState) { + Debug.Log($"[Non-Auth]{name} State Updated."); StateUpdated = true; base.OnNetworkTransformStateUpdated(ref oldState, ref newState); } + protected string GetVector3Values(ref Vector3 vector3) + { + return $"({vector3.x:F6},{vector3.y:F6},{vector3.z:F6})"; + } + + protected string GetVector3Values(Vector3 vector3) + { + return GetVector3Values(ref vector3); + } + + public bool EnableVerboseDebug; + + public void GetInterpolatorInfo() + { + if (!EnableVerboseDebug) + { + return; + } + var positionInterpolator = GetPositionInterpolator(); + var rotationInterpolator = GetRotationInterpolator(); + Debug.Log($"TT: {positionInterpolator.InterpolateState.TimeToTargetValue} BuffCnt: {positionInterpolator.m_BufferQueue.Count} Pos: {GetVector3Values(positionInterpolator.InterpolateState.CurrentValue)} " + + $"Rot: {GetVector3Values(rotationInterpolator.InterpolateState.CurrentValue.eulerAngles)}"); + } + + public override void OnUpdate() + { + base.OnUpdate(); + GetInterpolatorInfo(); + } + protected override bool OnIsServerAuthoritative() { return ServerAuthority; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 3aba8baa01..9a3de33404 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -492,7 +492,7 @@ public void ParentedNetworkTransformTest([Values] Interpolation interpolation, [ /// delta update, and it runs through 8 delta updates per unique test. /// [Test] - public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] OverrideState overideState, [Values] Axis axis) + public void MultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] OverrideState overideState, [Values] Axis axis) { m_AuthoritativeTransform.InLocalSpace = testLocalTransform == TransformSpace.Local; bool axisX = axis == Axis.X || axis == Axis.XY || axis == Axis.XZ || axis == Axis.XYZ; @@ -507,6 +507,30 @@ public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace test // when interpolation is enabled. m_AuthoritativeTransform.Interpolate = axisCount == 3 ? true : false; + // Lerp smoothing skews values based on our tests and how we had to originally adjust for the way we handled the original Lerp approach and how that + // consumed state updates from the buffer. + // With the two new interpolation types, they will process until close to the final value before moving on to the next. + // Lerp--> Will skip to next state before finishing the current state (i.e. loss of precision but arrives to the final value at the end of multiple updates faster) + // LerpExtrapolateBlend & SmoothDampening --> + // Will not skip to the next state update until approximately at the end of the current state (higher precision longer time to final value) + // How this impacts this test: + // It was re-written to use TimeTravel which has a limit of 60 update iterations per "WaitforCondition" which if you are interpolating between two large values + // it can take a few more iterations with lerp smoothing enabled. Lerp smoothing is purely a visual thing and will eventually end up at its final destination + // upon processing the last state update. However, this test should be only to verify the functionality of the actual lerping between values without the added + // delay of smoothing the final result. So, instead of having one timeout value for the two new interpolation types and the default for the original I am opting + // for the disabling of lerp smoothing while this particular test runs as it really is only validating that each interpolator type will interpolate to the right + // value within a given period of time which is simulated using the time travel approach. + // With smooth lerping enabled, the two new interpolation types will come very close to the correct value but will not reach the 2nd or 3rd pass values set because + // this test uses the adjusted approximation checks that prematurely determines the target values (position, rotation, and scale) have been reached and as such + // sends a new state update that will sit in the buffer for 3-4 frames before the two new interpolation types are done with the current state update. This will + // eventually lead to a time deficit that will offset the processing of the next state update such that the default time travel timeout (60 updates) will timeout + // and the test will fail. This only happens with 3 axis since that is the only time interpolation was enabled for this particular test. + // As such, just disabling smooth lerping for all 3 seemed like the better approach as the maximum interpolation time out period for smooth lerping is now + // adjustable by users (i.e. they can adjust how much lerp smoothing is applied based on their project's needs). + m_NonAuthoritativeTransform.PositionLerpSmoothing = false; + m_NonAuthoritativeTransform.RotationLerpSmoothing = false; + m_NonAuthoritativeTransform.ScaleLerpSmoothing = false; + m_CurrentAxis = axis; m_AuthoritativeTransform.SyncPositionX = axisX; @@ -541,12 +565,18 @@ public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace test var scale = scaleStart; var success = false; - m_AuthoritativeTransform.StatePushed = false; - // Wait for the deltas to be pushed - WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed); - // Allow the precision settings to propagate first as changing precision - // causes a teleport event to occur - TimeTravelAdvanceTick(); + // The default is interpolate, so we only need to check for the updated state when + // we turn off interpolation. + if (!m_AuthoritativeTransform.Interpolate) + { + // Reset our state updated and state pushed + m_NonAuthoritativeTransform.StateUpdated = false; + m_AuthoritativeTransform.StatePushed = false; + // Wait for both authority and non-authority to update their respective flags so we know the change to interpolation has been received. + success = WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed && m_NonAuthoritativeTransform.StateUpdated); + Assert.True(success, "Failed to wait for interpolation changed state update!"); + } + var iterations = axisCount == 3 ? k_PositionRotationScaleIterations3Axis : k_PositionRotationScaleIterations; // Move and rotate within the same tick, validate the non-authoritative instance updates @@ -595,6 +625,7 @@ public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace test if (!success) { m_EnableVerboseDebug = true; + VerboseDebug($"Failed on iteration: {i}"); success = PositionRotationScaleMatches(); m_EnableVerboseDebug = false; } From bb7bfa0ed9b19c7929116fae5e1925c59f03b088 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 28 Mar 2025 18:59:19 -0500 Subject: [PATCH 41/50] style removing unused property. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 1e5ab5e62f..a8a2096c26 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -249,8 +249,6 @@ public void Reset(T currentValue) /// private protected bool m_IsAngularValue; - private bool m_WasPredictedLerp; - /// /// Resets interpolator to the defaults. /// From f327a1e474dd6f02efc683882493a86c142d0c83 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sun, 30 Mar 2025 13:18:48 -0500 Subject: [PATCH 42/50] update Some clean up, renaming, and removing the change in the original BufferedLinearInterpolator.ResetTo method. --- .../BufferedLinearInterpolator.cs | 74 +++++++------------ .../BufferedLinearInterpolatorFloat.cs | 9 +-- .../BufferedLinearInterpolatorVector3.cs | 12 +-- 3 files changed, 29 insertions(+), 66 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index a8a2096c26..df11a2dc2a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -131,22 +131,14 @@ internal struct CurrentState public T Phase1Value; public T Phase2Value; - private float m_AverageDeltaTime; + private float m_CurrentDeltaTime; - public float AverageDeltaTime => m_AverageDeltaTime; + public float CurrentDeltaTime => m_CurrentDeltaTime; public double FinalTimeToTarget => Math.Max(0.0, TimeToTargetValue - DeltaTime); public void AddDeltaTime(float deltaTime) { - if (m_AverageDeltaTime == 0.0f) - { - m_AverageDeltaTime = deltaTime; - } - else - { - m_AverageDeltaTime += deltaTime; - m_AverageDeltaTime *= 0.5f; - } + m_CurrentDeltaTime = deltaTime; DeltaTime = Math.Min(DeltaTime + deltaTime, TimeToTargetValue); DeltaTimePredict = Math.Min(DeltaTime + deltaTime, TimeToTargetValue + MaxDeltaTime); LerpT = (float)(TimeToTargetValue == 0.0 ? 1.0 : DeltaTime / TimeToTargetValue); @@ -162,7 +154,6 @@ public void AddDeltaTime(float deltaTime) public void SetTimeToTarget(double timeToTarget) { - m_AverageDeltaTime = 0.0f; DeltaTimePredict = 0.0f; LerpTPredict = 0.0f; LerpT = 0.0f; @@ -170,13 +161,13 @@ public void SetTimeToTarget(double timeToTarget) TimeToTargetValue = timeToTarget; } - public bool TargetTimeAproximatelyReached(float adjustForNext = 1.0f) + public bool TargetTimeAproximatelyReached(bool nextStatePending) { if (!Target.HasValue) { return false; } - return (m_AverageDeltaTime * adjustForNext) >= FinalTimeToTarget; + return (m_CurrentDeltaTime * (nextStatePending ? (LerpT * 1.30f) : 1.0f)) >= FinalTimeToTarget; } public void Reset(T currentValue) @@ -196,7 +187,7 @@ public void Reset(T currentValue) TimeToTargetValue = 0.0f; DeltaTime = 0.0f; DeltaTimePredict = 0.0f; - m_AverageDeltaTime = 0.0f; + m_CurrentDeltaTime = 0.0f; } } @@ -244,11 +235,6 @@ public void Reset(T currentValue) /// private T m_PredictedRateOfChange; - /// - /// When true, the value is an angular numeric representation. - /// - private protected bool m_IsAngularValue; - /// /// Resets interpolator to the defaults. /// @@ -270,21 +256,19 @@ public void Clear() /// /// The target value to reset the interpolator to /// The current server time - /// When rotation is expressed as Euler values (i.e. Vector3 and/or float) this helps determine what kind of smooth dampening to use. - public void ResetTo(T targetValue, double serverTime, bool isAngularValue = false) + public void ResetTo(T targetValue, double serverTime) { // Clear the interpolator Clear(); - InternalReset(targetValue, serverTime, isAngularValue); + InternalReset(targetValue, serverTime); } - private void InternalReset(T targetValue, double serverTime, bool isAngularValue = false, bool addMeasurement = true) + private void InternalReset(T targetValue, double serverTime, bool addMeasurement = true) { m_RateOfChange = default; m_PredictedRateOfChange = default; // Set our initial value InterpolateState.Reset(targetValue); - m_IsAngularValue = isAngularValue; if (addMeasurement) { @@ -297,10 +281,11 @@ private void InternalReset(T targetValue, double serverTime, bool isAngularValue /// /// TryConsumeFromBuffer: Smooth Dampening Version /// - /// render time: the time in "ticks ago" relative to the current tick latency - /// minimum time delta (defaults to tick frequency) - /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer - private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double maxDeltaTime, bool isPredictedLerp) + /// render time: the time in "ticks ago" relative to the current tick latency. + /// minimum time delta (defaults to tick frequency). + /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer. + /// when true, the predicted target will lerp slightly ahead of the target. + private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double maxDeltaTime, bool extrapolateAhead) { BufferedItem? previousItem = null; var startTime = 0.0; @@ -333,7 +318,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double if (!noStateSet) { potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent >= InterpolateState.Target.Value.TimeSent; - currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing ? 1.15f : 1.0f) || InterpolateState.TargetReached; + currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing) || InterpolateState.TargetReached; if (!InterpolateState.TargetReached) { InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); @@ -344,7 +329,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double // then pull the BufferedItem from the queue. The second portion of this accounts for scenarios where there // was bad latency and the buffer has more than one item in the queue that is less than the renderTime. Under // this scenario, we just want to continue pulling items from the queue until the last item pulled from the - // queue is greater than the redner time or greater than the currently targeted item. + // queue is greater than the redner time or greater than the currently targeted item's sent time. if (noStateSet || ((currentTargetTimeReached || InterpolateState.TargetReached) && potentialItemNeedsProcessing)) { if (m_BufferQueue.TryDequeue(out BufferedItem target)) @@ -372,21 +357,16 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double alreadyHasBufferItem = true; InterpolateState.TargetReached = false; startTime = InterpolateState.Target.Value.TimeSent; - if (isPredictedLerp) - { - InterpolateState.Phase1Value = InterpolateState.PreviousValue; - InterpolateState.Phase2Value = Interpolate(InterpolateState.PredictValue, target.Item, InterpolateState.AverageDeltaTime); - } - else - { - InterpolateState.PredictValue = InterpolateState.PreviousValue; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; - } InterpolateState.MaxDeltaTime = maxDeltaTime; InterpolateState.PredictingNext = m_BufferQueue.Count > 0; + InterpolateState.Phase1Value = InterpolateState.PreviousValue; + if (extrapolateAhead && InterpolateState.PredictingNext) + { + InterpolateState.Phase2Value = InterpolateState.PredictingNext ? Interpolate(InterpolateState.PredictValue, target.Item, InterpolateState.CurrentDeltaTime) + : InterpolateState.Phase2Value = InterpolateState.PredictValue; + } } - // We continue to stretch the time out if we are far behind in processing the buffer. - // TODO: We need to compress time when there is a large amount of items to be processed. + InterpolateState.SetTimeToTarget(Math.Max((float)(target.TimeSent - startTime), minDeltaTime)); InterpolateState.Target = target; } @@ -444,8 +424,8 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime // SmoothDampen or LerpExtrapolateBlend if (!isLerpAndExtrapolate) { - InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.DeltaTime); - InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)(InterpolateState.DeltaTime + deltaTime)); + InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.TimeToTargetValue * InterpolateState.LerpT); + InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)(InterpolateState.TimeToTargetValue * InterpolateState.LerpTPredict)); } else { @@ -645,7 +625,7 @@ public void AddMeasurement(T newMeasurement, double sentTime) // Clear the interpolator Clear(); // Reset to the new value but don't automatically add the measurement (prevents recursion) - InternalReset(newMeasurement, sentTime, m_IsAngularValue, false); + InternalReset(newMeasurement, sentTime, false); m_LastMeasurementAddedTime = sentTime; m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); // Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues @@ -654,7 +634,7 @@ public void AddMeasurement(T newMeasurement, double sentTime) return; } - // Drop measurements that are received out of order/late + // Drop measurements that are received out of order/late (i.e. user unreliable delta) if (sentTime > m_LastMeasurementAddedTime || m_BufferCount == 0) { m_BufferCount++; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs index b1a0b98474..77583468a7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs @@ -34,14 +34,7 @@ private protected override bool IsAproximately(float first, float second, float [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override float SmoothDamp(float current, float target, ref float rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) { - if (m_IsAngularValue) - { - return Mathf.SmoothDampAngle(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); - } - else - { - return Mathf.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); - } + return Mathf.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs index e5d66b73cd..aa5a739683 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs @@ -70,17 +70,7 @@ private protected override bool IsAproximately(Vector3 first, Vector3 second, fl [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected override Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 rateOfChange, float duration, float deltaTime, float maxSpeed) { - if (m_IsAngularValue) - { - current.x = Mathf.SmoothDampAngle(current.x, target.x, ref rateOfChange.x, duration, maxSpeed, deltaTime); - current.y = Mathf.SmoothDampAngle(current.y, target.y, ref rateOfChange.y, duration, maxSpeed, deltaTime); - current.z = Mathf.SmoothDampAngle(current.z, target.z, ref rateOfChange.z, duration, maxSpeed, deltaTime); - return current; - } - else - { - return Vector3.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); - } + return Vector3.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); } } } From 5e7af3701c682d8e7584bc53878d0bd7680f5f22 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 1 Apr 2025 18:52:27 -0500 Subject: [PATCH 43/50] update Renamed LerpExtrapolateBlend to LerpAhead. Found some issues with the original lerp's buffer consumption where it was always calculating 2x the time to target. Assuring that the original lerp logic isn't executed upon arriving to the final destination. Increasing the k_AproximatePrecision...preceision value to be one one hundred thousandth. Removed some debug code from NetworkTransformBase. --- .../BufferedLinearInterpolator.cs | 172 +++++++++--------- .../Runtime/Components/NetworkTransform.cs | 42 +++-- .../NetworkTransform/NetworkTransformBase.cs | 20 -- .../NetworkTransformGeneral.cs | 4 +- .../NetworkTransform/NetworkTransformTests.cs | 38 ++-- 5 files changed, 137 insertions(+), 139 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index df11a2dc2a..086254eabb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -14,7 +14,7 @@ public abstract class BufferedLinearInterpolator where T : struct // Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so // that we don't have a very small buffer because of this. private const int k_BufferCountLimit = 100; - private const float k_AproximatePrecision = 0.0001f; + private const float k_AproximatePrecision = 0.00001f; private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator #region Legacy notes @@ -277,23 +277,21 @@ private void InternalReset(T targetValue, double serverTime, bool addMeasurement } } - #region Smooth Dampening and Lerp Extrapolate Blend Interpolation Handling + #region Smooth Dampening and Lerp Ahead Interpolation Handling /// - /// TryConsumeFromBuffer: Smooth Dampening Version + /// TryConsumeFromBuffer: Smooth Dampening and Lerp Ahead Version /// /// render time: the time in "ticks ago" relative to the current tick latency. /// minimum time delta (defaults to tick frequency). /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer. - /// when true, the predicted target will lerp slightly ahead of the target. - private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double maxDeltaTime, bool extrapolateAhead) + /// when true, the predicted target will lerp towards the next target by the current delta. + private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double maxDeltaTime, bool lerpAhead) { BufferedItem? previousItem = null; var startTime = 0.0; var alreadyHasBufferItem = false; var noStateSet = !InterpolateState.Target.HasValue; var potentialItemNeedsProcessing = false; - var currentTargetTimeReached = false; - var dequeuedCount = 0; // In the event there is nothing left in the queue (i.e. motion/change stopped), we still need to determine if the target has been reached. if (!noStateSet && m_BufferQueue.Count == 0) @@ -317,24 +315,18 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double if (!noStateSet) { - potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent >= InterpolateState.Target.Value.TimeSent; - currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing) || InterpolateState.TargetReached; + potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent; if (!InterpolateState.TargetReached) { InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); } } - // If we haven't set a target or the potential item's time sent is less that the current target's time sent - // then pull the BufferedItem from the queue. The second portion of this accounts for scenarios where there - // was bad latency and the buffer has more than one item in the queue that is less than the renderTime. Under - // this scenario, we just want to continue pulling items from the queue until the last item pulled from the - // queue is greater than the redner time or greater than the currently targeted item's sent time. - if (noStateSet || ((currentTargetTimeReached || InterpolateState.TargetReached) && potentialItemNeedsProcessing)) + // If we haven't set a target or we have another item that needs processing. + if (noStateSet || potentialItemNeedsProcessing) { if (m_BufferQueue.TryDequeue(out BufferedItem target)) { - dequeuedCount++; if (!InterpolateState.Target.HasValue) { InterpolateState.Target = target; @@ -359,15 +351,20 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double startTime = InterpolateState.Target.Value.TimeSent; InterpolateState.MaxDeltaTime = maxDeltaTime; InterpolateState.PredictingNext = m_BufferQueue.Count > 0; - InterpolateState.Phase1Value = InterpolateState.PreviousValue; - if (extrapolateAhead && InterpolateState.PredictingNext) + if (lerpAhead) { - InterpolateState.Phase2Value = InterpolateState.PredictingNext ? Interpolate(InterpolateState.PredictValue, target.Item, InterpolateState.CurrentDeltaTime) - : InterpolateState.Phase2Value = InterpolateState.PredictValue; + InterpolateState.Phase1Value = InterpolateState.PreviousValue; + if (InterpolateState.PredictingNext && m_BufferQueue.TryPeek(out BufferedItem nextTarget)) + { + InterpolateState.Phase2Value = Interpolate(InterpolateState.PredictValue, nextTarget.Item, InterpolateState.CurrentDeltaTime); + } + else + { + InterpolateState.Phase2Value = InterpolateState.PredictValue; + } } } - - InterpolateState.SetTimeToTarget(Math.Max((float)(target.TimeSent - startTime), minDeltaTime)); + InterpolateState.SetTimeToTarget(Math.Max(target.TimeSent - startTime, minDeltaTime)); InterpolateState.Target = target; } } @@ -406,11 +403,11 @@ internal void ResetCurrentState() /// The tick latency in relative local time. /// The minimum time delta between the current and target value. /// The maximum time delta between the current and target value. - /// Determines whether to use smooth dampening or extrapolation. + /// Determines whether to use smooth dampening or lerp ahead interpolation type. /// The newly interpolated value of type 'T' - internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime, double maxDeltaTime, bool isLerpAndExtrapolate) + internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime, double maxDeltaTime, bool lerpAhead) { - TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime, isLerpAndExtrapolate); + TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime, lerpAhead); // Only begin interpolation when there is a start and end point if (InterpolateState.Target.HasValue) { @@ -421,20 +418,21 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime // Also calculates the LerpT and LerpTPredicted values. InterpolateState.AddDeltaTime(deltaTime); var targetValue = InterpolateState.CurrentValue; - // SmoothDampen or LerpExtrapolateBlend - if (!isLerpAndExtrapolate) + // SmoothDampen + if (!lerpAhead) { InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.TimeToTargetValue * InterpolateState.LerpT); InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)(InterpolateState.TimeToTargetValue * InterpolateState.LerpTPredict)); } - else + else// Lerp Ahead { InterpolateState.PreviousValue = Interpolate(InterpolateState.Phase1Value, InterpolateState.Target.Value.Item, InterpolateState.LerpT); // Note: InterpolateState.LerpTPredict is clamped to LerpT if we have no next target InterpolateState.PredictValue = InterpolateUnclamped(InterpolateState.Phase2Value, InterpolateState.Target.Value.Item, InterpolateState.LerpTPredict); + } - - // Lerp between the PreviousValue and PredictedValue using the calculated time delta + + // Lerp between the PreviousValue and PredictedValue using the current delta time targetValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime); // If lerp smoothing is enabled, then smooth current value towards the target value @@ -477,59 +475,58 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime /// This version of TryConsumeFromBuffer adheres to the original BufferedLinearInterpolator buffer consumption pattern. /// /// - /// - private void TryConsumeFromBuffer(double renderTime, double serverTime) + private void TryConsumeFromBuffer(double renderTime) { - if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime)) + BufferedItem? previousItem = null; + var alreadyHasBufferItem = false; + while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) { - BufferedItem? previousItem = null; - var alreadyHasBufferItem = false; - while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) + // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing + // to consume. + if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) { - // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing - // to consume. - if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) - { - break; - } + break; + } - if ((potentialItem.TimeSent <= serverTime) && - (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) + if ((potentialItem.TimeSent <= renderTime) && + (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) + { + if (m_BufferQueue.TryDequeue(out BufferedItem target)) { - if (m_BufferQueue.TryDequeue(out BufferedItem target)) + if (!InterpolateState.Target.HasValue) + { + InterpolateState.Target = target; + alreadyHasBufferItem = true; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.StartTime = target.TimeSent; + InterpolateState.EndTime = target.TimeSent; + InterpolateState.TargetReached = false; + } + else { - if (!InterpolateState.Target.HasValue) + if (!alreadyHasBufferItem) { - InterpolateState.Target = target; alreadyHasBufferItem = true; + InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent; InterpolateState.PreviousValue = InterpolateState.CurrentValue; - InterpolateState.StartTime = target.TimeSent; - InterpolateState.EndTime = target.TimeSent; - } - else - { - if (!alreadyHasBufferItem) - { - alreadyHasBufferItem = true; - InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; - } - InterpolateState.EndTime = target.TimeSent; - InterpolateState.Target = target; + InterpolateState.TargetReached = false; } + InterpolateState.EndTime = target.TimeSent; + InterpolateState.Target = target; + InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime; } } - else - { - break; - } + } + else + { + break; + } - if (!InterpolateState.Target.HasValue) - { - break; - } - previousItem = potentialItem; + if (!InterpolateState.Target.HasValue) + { + break; } + previousItem = potentialItem; } } @@ -542,31 +539,22 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) /// time since last call /// our current time /// current server time + /// + /// We no longer use serverTime. + /// /// The newly interpolated value of type 'T' public T Update(float deltaTime, double renderTime, double serverTime) { - TryConsumeFromBuffer(renderTime, serverTime); + TryConsumeFromBuffer(renderTime); // Only interpolate when there is a start and end point and we have not already reached the end value - if (InterpolateState.Target.HasValue) + if (InterpolateState.Target.HasValue && !InterpolateState.TargetReached) { // The original BufferedLinearInterpolator lerping script to assure the Smooth Dampening updates do not impact // this specific behavior. float t = 1.0f; - InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime; if (InterpolateState.TimeToTargetValue > k_SmallValue) { - t = (float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue); - - if (t < 0.0f) - { - t = 0.0f; - } - - if (t > MaxInterpolationBound) // max extrapolation - { - // TODO this causes issues with teleport, investigate - t = 1.0f; - } + t = Math.Clamp((float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue), 0.0f, 1.0f); } var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, t); @@ -579,6 +567,22 @@ public T Update(float deltaTime, double renderTime, double serverTime) { InterpolateState.CurrentValue = target; } + // Determine if we have reached our target + InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); + } + else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. + if (m_BufferQueue.Count == 0 && InterpolateState.TargetReached) + { + // When the delta between the time sent and the current tick latency time-window is greater than the max delta time + // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), + // we will want to reset the interpolator with the current known value. This prevents the next received state update's + // time to be calculated against the last calculated time which if there is an extended period of time between the two + // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then + // starts moving again). + if ((renderTime - InterpolateState.Target.Value.TimeSent) > 0.3f) // If we haven't recevied anything within 300ms, assume we stopped motion. + { + InterpolateState.Reset(InterpolateState.CurrentValue); + } } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 64dd3fc7c5..d9ade1c2e9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -983,11 +983,11 @@ public enum InterpolationTypes /// Lerp, /// - /// Lerp, Extrapolate, and Blend - /// Uses a 3 to 4 phase approach that lerps towards the target, extrapolate towards the target, blends the two results, and (optionally) smooth the final value.
+ /// Lerp Ahead + /// Uses a 3 to 4 phase approach that lerps towards the target, lerps towards the next target (if one exists) ahead by 1 frame delta, blends the two results, and (optionally) smooth the final value.
/// /// The first phase lerps towards the current tick state update being processed. - /// The second phase lerps unclamped (extrapolates) towards the current tick state update and will extrapolate this value up to a calculated maximum state update delta time. + /// The second phase lerps unclamped (extrapolates) towards the next tick state update. If there is no next target, then it lerps towards the current target unclamped with "t" being clamped to 1.0. /// The third phase lerps between the results of the first and second phases by the current delta time. /// The fourth phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. /// @@ -1005,7 +1005,7 @@ public enum InterpolationTypes /// /// /// - LerpExtrapolateBlend, + LerpAhead, /// /// Uses a 3 to 4 phase approach that smooth dampens towards the target, smooth dampens ahead of the target, blends the two results, and (optionally) smooth the final value.
/// @@ -1016,7 +1016,7 @@ public enum InterpolationTypes /// ///
/// - /// Note: Smooth dampening is computationally more expensive than the and approaches.
+ /// Note: Smooth dampening is computationally more expensive than the and approaches.
/// It is recommended to turn lerp smoothing off or adjust the interpolation time to a lower value if you want a more precise end result. /// For more information:
/// @@ -1037,7 +1037,7 @@ public enum InterpolationTypes /// /// /// Yields a traditional linear result. - /// Lerps, extrapolates, and blends the results. + /// Lerps, extrapolates, and blends the results. /// Adjusts based on the rate of change. /// /// Things to consider:
@@ -1056,7 +1056,7 @@ public enum InterpolationTypes /// /// /// Yields a traditional linear result. - /// Lerps, extrapolates, and blends the results. + /// Lerps, extrapolates, and blends the results. /// Adjusts based on the rate of change. /// /// Things to consider:
@@ -1075,7 +1075,7 @@ public enum InterpolationTypes /// /// /// Yields a traditional linear result. - /// Lerps, extrapolates, and blends the results. + /// Lerps, extrapolates, and blends the results. /// Adjusts based on the rate of change. /// /// Things to consider:
@@ -4004,6 +4004,7 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca private NetworkTransformTickRegistration m_NetworkTransformTickRegistration; #if DEBUG_LINEARBUFFER && UNITY_EDITOR +#if UNITY_EDITOR // For debugging purposes public struct BufferEntry { @@ -4044,16 +4045,29 @@ public void ClearStats() { PositionStats.Clear(); } -#endif - internal BufferedLinearInterpolatorVector3 GetPositionInterpolator() + + public BufferedLinearInterpolatorVector3 GetPositionInterpolator() { return m_PositionInterpolator; } - internal BufferedLinearInterpolatorQuaternion GetRotationInterpolator() + public BufferedLinearInterpolatorQuaternion GetRotationInterpolator() { return m_RotationInterpolator; } +#endif + public int GetPositionBufferCount() + { + return m_PositionInterpolator.m_BufferQueue.Count; + } + + public double GetPositionCurrentStateTimeToTarget() + { + return m_PositionInterpolator.InterpolateState.TimeToTargetValue; + } +#endif + + // Non-Authority private void UpdateInterpolation() @@ -4134,7 +4148,7 @@ private void UpdateInterpolation() else { m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, - PositionInterpolationType == InterpolationTypes.LerpExtrapolateBlend); + PositionInterpolationType == InterpolationTypes.LerpAhead); } } @@ -4165,7 +4179,7 @@ private void UpdateInterpolation() else { m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, - RotationInterpolationType == InterpolationTypes.LerpExtrapolateBlend); + RotationInterpolationType == InterpolationTypes.LerpAhead); } } @@ -4193,7 +4207,7 @@ private void UpdateInterpolation() else { m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, - ScaleInterpolationType == InterpolationTypes.LerpExtrapolateBlend); + ScaleInterpolationType == InterpolationTypes.LerpAhead); } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs index 487662ee1a..cd8abe22ad 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformBase.cs @@ -808,26 +808,6 @@ protected string GetVector3Values(Vector3 vector3) return GetVector3Values(ref vector3); } - public bool EnableVerboseDebug; - - public void GetInterpolatorInfo() - { - if (!EnableVerboseDebug) - { - return; - } - var positionInterpolator = GetPositionInterpolator(); - var rotationInterpolator = GetRotationInterpolator(); - Debug.Log($"TT: {positionInterpolator.InterpolateState.TimeToTargetValue} BuffCnt: {positionInterpolator.m_BufferQueue.Count} Pos: {GetVector3Values(positionInterpolator.InterpolateState.CurrentValue)} " + - $"Rot: {GetVector3Values(rotationInterpolator.InterpolateState.CurrentValue.eulerAngles)}"); - } - - public override void OnUpdate() - { - base.OnUpdate(); - GetInterpolatorInfo(); - } - protected override bool OnIsServerAuthoritative() { return ServerAuthority; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 24b348a0cb..56cfacdfcb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -8,8 +8,8 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.LerpAhead)] internal class NetworkTransformGeneral : NetworkTransformBase { public enum SmoothLerpSettings diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 9a3de33404..e3b4d95884 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests /// models for each operating mode. ///
[TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] @@ -20,11 +20,11 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] @@ -34,7 +34,7 @@ namespace Unity.Netcode.RuntimeTests #endif [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] @@ -43,11 +43,11 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] @@ -56,7 +56,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] #endif [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] @@ -65,11 +65,11 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpExtrapolateBlend)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] @@ -511,7 +511,7 @@ public void MultipleChangesOverTime([Values] TransformSpace testLocalTransform, // consumed state updates from the buffer. // With the two new interpolation types, they will process until close to the final value before moving on to the next. // Lerp--> Will skip to next state before finishing the current state (i.e. loss of precision but arrives to the final value at the end of multiple updates faster) - // LerpExtrapolateBlend & SmoothDampening --> + // LerpAhead & SmoothDampening --> // Will not skip to the next state update until approximately at the end of the current state (higher precision longer time to final value) // How this impacts this test: // It was re-written to use TimeTravel which has a limit of 60 update iterations per "WaitforCondition" which if you are interpolating between two large values From 89739c77702c7dec7aca243e3f2e06b65e8bc7e3 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 1 Apr 2025 18:59:52 -0500 Subject: [PATCH 44/50] style Adjusting changelog entry to replace LerpExtrapolateBlend with LerpAhead. Removing additional remarks tag. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 +- .../Components/Interpolator/BufferedLinearInterpolator.cs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c9b8962ef1..b12b6c755e 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,7 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added - Added `NetworkManager.OnPreShutdown` which is called before the NetworkManager cleans up and shuts down. (#3366) -- Added `LerpExtrapolateBlend` interpolation type that provides users with something between standard lerp and smooth dampening. (#3355) +- Added `LerpAhead` interpolation type that provides users with something between standard lerp and smooth dampening. (#3355) - Added property to enable or disable lerp smoothing for position, rotation, and scale interpolators. (#3355) - Added `NetworkTransform.InterpolationBufferTickOffset` static property to provide users with a way to increase or decrease the time marker where interpolators will pull state update from the queue. (#3355) - Added interpolator types as an inspector view selection for position, rotation, and scale. (#3337) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 086254eabb..c16dd1b909 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -539,9 +539,6 @@ private void TryConsumeFromBuffer(double renderTime) /// time since last call /// our current time /// current server time - /// - /// We no longer use serverTime. - /// /// The newly interpolated value of type 'T' public T Update(float deltaTime, double renderTime, double serverTime) { From 1202fe796a5ca9f6f2b69970c29487843f4969ba Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 1 Apr 2025 19:02:09 -0500 Subject: [PATCH 45/50] test Updating the test project test for change in LerpAhead. --- .../NestedNetworkTransformTests.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs index 9dff9e141c..4713a7861b 100644 --- a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs @@ -36,18 +36,18 @@ namespace TestProject.RuntimeTests [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] // Lerp, extrapolate, and blend pass - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpExtrapolateBlend, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] // Smooth dampening interpolation pass [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] From 5b0f3929874fb06a84bfe141a10ffa88fdc00312 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 1 Apr 2025 23:11:50 -0500 Subject: [PATCH 46/50] update Reverting the change to fix that makes the original lerp properly calculate the time to target as opposed to always calculating 2x the time to target because all of the unit interpolator tests are specifically written for this specific flaw. --- .../Interpolator/BufferedLinearInterpolator.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index c16dd1b909..e712a12ae4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -475,7 +475,7 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime /// This version of TryConsumeFromBuffer adheres to the original BufferedLinearInterpolator buffer consumption pattern. /// /// - private void TryConsumeFromBuffer(double renderTime) + private void TryConsumeFromBuffer(double renderTime, double serverTime) { BufferedItem? previousItem = null; var alreadyHasBufferItem = false; @@ -488,7 +488,7 @@ private void TryConsumeFromBuffer(double renderTime) break; } - if ((potentialItem.TimeSent <= renderTime) && + if ((potentialItem.TimeSent <= serverTime) && (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) { if (m_BufferQueue.TryDequeue(out BufferedItem target)) @@ -500,7 +500,6 @@ private void TryConsumeFromBuffer(double renderTime) InterpolateState.PreviousValue = InterpolateState.CurrentValue; InterpolateState.StartTime = target.TimeSent; InterpolateState.EndTime = target.TimeSent; - InterpolateState.TargetReached = false; } else { @@ -542,7 +541,7 @@ private void TryConsumeFromBuffer(double renderTime) /// The newly interpolated value of type 'T' public T Update(float deltaTime, double renderTime, double serverTime) { - TryConsumeFromBuffer(renderTime); + TryConsumeFromBuffer(renderTime, serverTime); // Only interpolate when there is a start and end point and we have not already reached the end value if (InterpolateState.Target.HasValue && !InterpolateState.TargetReached) { @@ -603,9 +602,9 @@ public T Update(float deltaTime, NetworkTime serverTime) /// /// Used for internal testing /// - internal T UpdateInternal(float deltaTime, NetworkTime serverTime) + internal T UpdateInternal(float deltaTime, NetworkTime serverTime, int ticksAgo = 1) { - return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); + return Update(deltaTime, serverTime.TimeTicksAgo(ticksAgo).Time, serverTime.Time); } /// From ac55f4877003eae06b1cfccfbfcebb478cad933f Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 1 Apr 2025 23:47:07 -0500 Subject: [PATCH 47/50] style Whitespace fixes. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index e712a12ae4..a31ee64eae 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -429,9 +429,9 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime InterpolateState.PreviousValue = Interpolate(InterpolateState.Phase1Value, InterpolateState.Target.Value.Item, InterpolateState.LerpT); // Note: InterpolateState.LerpTPredict is clamped to LerpT if we have no next target InterpolateState.PredictValue = InterpolateUnclamped(InterpolateState.Phase2Value, InterpolateState.Target.Value.Item, InterpolateState.LerpTPredict); - + } - + // Lerp between the PreviousValue and PredictedValue using the current delta time targetValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime); From c0d794f4ce9b2078a3d750628e23d3e593963097 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 2 Apr 2025 13:18:38 -0500 Subject: [PATCH 48/50] update Still had one more line of code needed to go back to original lerp buffer consumption where the time to target is always 2x the actual time to target (it ends up consuming 2 state updates for each non-authority update). Making sure some of the debugging BLI accessor methods in NetworkTransform are still accessible for internal testing purposes. --- .../BufferedLinearInterpolator.cs | 75 ++++++++++--------- .../Runtime/Components/NetworkTransform.cs | 2 +- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index a31ee64eae..b569a06106 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -477,55 +477,58 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime /// private void TryConsumeFromBuffer(double renderTime, double serverTime) { - BufferedItem? previousItem = null; - var alreadyHasBufferItem = false; - while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) + if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime)) { - // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing - // to consume. - if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) + BufferedItem? previousItem = null; + var alreadyHasBufferItem = false; + while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) { - break; - } + // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing + // to consume. + if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) + { + break; + } - if ((potentialItem.TimeSent <= serverTime) && - (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) - { - if (m_BufferQueue.TryDequeue(out BufferedItem target)) + if ((potentialItem.TimeSent <= serverTime) && + (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) { - if (!InterpolateState.Target.HasValue) - { - InterpolateState.Target = target; - alreadyHasBufferItem = true; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; - InterpolateState.StartTime = target.TimeSent; - InterpolateState.EndTime = target.TimeSent; - } - else + if (m_BufferQueue.TryDequeue(out BufferedItem target)) { - if (!alreadyHasBufferItem) + if (!InterpolateState.Target.HasValue) { + InterpolateState.Target = target; alreadyHasBufferItem = true; - InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent; InterpolateState.PreviousValue = InterpolateState.CurrentValue; - InterpolateState.TargetReached = false; + InterpolateState.StartTime = target.TimeSent; + InterpolateState.EndTime = target.TimeSent; + } + else + { + if (!alreadyHasBufferItem) + { + alreadyHasBufferItem = true; + InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.TargetReached = false; + } + InterpolateState.EndTime = target.TimeSent; + InterpolateState.Target = target; + InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime; } - InterpolateState.EndTime = target.TimeSent; - InterpolateState.Target = target; - InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime; } } - } - else - { - break; - } + else + { + break; + } - if (!InterpolateState.Target.HasValue) - { - break; + if (!InterpolateState.Target.HasValue) + { + break; + } + previousItem = potentialItem; } - previousItem = potentialItem; } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index d9ade1c2e9..58afb854b2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -4003,7 +4003,7 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca #region UPDATES AND AUTHORITY CHECKS private NetworkTransformTickRegistration m_NetworkTransformTickRegistration; -#if DEBUG_LINEARBUFFER && UNITY_EDITOR +#if DEBUG_LINEARBUFFER #if UNITY_EDITOR // For debugging purposes public struct BufferEntry From b33b5469949b0482faceb06878121a0b4d30448d Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 2 Apr 2025 23:55:07 -0500 Subject: [PATCH 49/50] refactor After testing for quite some time I came to the conclusion that we needed a "new" lerp and should mark the original lerp as the "legacy" lerp while keeping the added option to have the smooth dampening. Removed the additional phases from the "new" lerp and smooth dampening while also providing users with a more direct and constant maximum interpolation setting that is not directly tied to the frame time delta. This yields a wider range of fine tuning and will be easier to handle adjusting during runtime. The LinearBufferInterpolator RNSM component hooks are in place so we can provide users with a tool to be able to determine what is happening with the LinearBufferInterpolator... it will be released in the examples section once ready to be released. Adjusted API XML documentation and all places that referenced the other enum names changed. --- .../BufferedLinearInterpolator.cs | 104 +++++------------- .../Runtime/Components/NetworkTransform.cs | 104 ++++++++---------- .../NetworkTransformGeneral.cs | 8 +- .../NetworkTransform/NetworkTransformTests.cs | 42 +++---- .../NestedNetworkTransformTests.cs | 26 ++--- 5 files changed, 108 insertions(+), 176 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index b569a06106..538a6ba1a1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -14,7 +14,7 @@ public abstract class BufferedLinearInterpolator where T : struct // Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so // that we don't have a very small buffer because of this. private const int k_BufferCountLimit = 100; - private const float k_AproximatePrecision = 0.00001f; + private const float k_AproximatePrecision = 0.0001f; private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator #region Legacy notes @@ -119,17 +119,13 @@ internal struct CurrentState public double EndTime; public double TimeToTargetValue; public double DeltaTime; - public double DeltaTimePredict; public double MaxDeltaTime; + public double LastRemainingTime; public float LerpT; - public float LerpTPredict; public bool TargetReached; - public bool PredictingNext; public T CurrentValue; public T PreviousValue; - public T PredictValue; - public T Phase1Value; - public T Phase2Value; + public T NextValue; private float m_CurrentDeltaTime; @@ -140,53 +136,37 @@ public void AddDeltaTime(float deltaTime) { m_CurrentDeltaTime = deltaTime; DeltaTime = Math.Min(DeltaTime + deltaTime, TimeToTargetValue); - DeltaTimePredict = Math.Min(DeltaTime + deltaTime, TimeToTargetValue + MaxDeltaTime); LerpT = (float)(TimeToTargetValue == 0.0 ? 1.0 : DeltaTime / TimeToTargetValue); - if (PredictingNext) - { - LerpTPredict = (float)(TimeToTargetValue == 0.0 ? 1.0 : DeltaTimePredict / TimeToTargetValue); - } - else - { - LerpTPredict = LerpT; - } } public void SetTimeToTarget(double timeToTarget) { - DeltaTimePredict = 0.0f; - LerpTPredict = 0.0f; LerpT = 0.0f; DeltaTime = 0.0f; TimeToTargetValue = timeToTarget; } - public bool TargetTimeAproximatelyReached(bool nextStatePending) + public bool TargetTimeAproximatelyReached() { if (!Target.HasValue) { return false; } - return (m_CurrentDeltaTime * (nextStatePending ? (LerpT * 1.30f) : 1.0f)) >= FinalTimeToTarget; + return FinalTimeToTarget <= m_CurrentDeltaTime * 0.5f; } public void Reset(T currentValue) { Target = null; CurrentValue = currentValue; + NextValue = currentValue; PreviousValue = currentValue; - PredictValue = currentValue; - Phase1Value = currentValue; - Phase2Value = currentValue; TargetReached = false; - PredictingNext = false; LerpT = 0.0f; - LerpTPredict = 0.0f; EndTime = 0.0; StartTime = 0.0; TimeToTargetValue = 0.0f; DeltaTime = 0.0f; - DeltaTimePredict = 0.0f; m_CurrentDeltaTime = 0.0f; } } @@ -230,11 +210,6 @@ public void Reset(T currentValue) /// private T m_RateOfChange; - /// - /// Represents the predicted rate of change for the value being interpolated when smooth dampening is enabled. - /// - private T m_PredictedRateOfChange; - /// /// Resets interpolator to the defaults. /// @@ -245,7 +220,6 @@ public void Clear() m_LastMeasurementAddedTime = 0.0; InterpolateState.Reset(default); m_RateOfChange = default; - m_PredictedRateOfChange = default; } /// @@ -266,7 +240,6 @@ public void ResetTo(T targetValue, double serverTime) private void InternalReset(T targetValue, double serverTime, bool addMeasurement = true) { m_RateOfChange = default; - m_PredictedRateOfChange = default; // Set our initial value InterpolateState.Reset(targetValue); @@ -285,7 +258,7 @@ private void InternalReset(T targetValue, double serverTime, bool addMeasurement /// minimum time delta (defaults to tick frequency). /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer. /// when true, the predicted target will lerp towards the next target by the current delta. - private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double maxDeltaTime, bool lerpAhead) + private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double maxDeltaTime) { BufferedItem? previousItem = null; var startTime = 0.0; @@ -315,7 +288,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double if (!noStateSet) { - potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent; + potentialItemNeedsProcessing = ((potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent); if (!InterpolateState.TargetReached) { InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); @@ -331,15 +304,11 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double { InterpolateState.Target = target; alreadyHasBufferItem = true; - InterpolateState.PredictValue = InterpolateState.CurrentValue; + InterpolateState.NextValue = InterpolateState.CurrentValue; InterpolateState.PreviousValue = InterpolateState.CurrentValue; - InterpolateState.Phase1Value = InterpolateState.CurrentValue; - InterpolateState.Phase2Value = InterpolateState.CurrentValue; InterpolateState.SetTimeToTarget(minDeltaTime); - InterpolateState.TimeToTargetValue = minDeltaTime; startTime = InterpolateState.Target.Value.TimeSent; InterpolateState.TargetReached = false; - InterpolateState.PredictingNext = false; InterpolateState.MaxDeltaTime = maxDeltaTime; } else @@ -347,22 +316,11 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double if (!alreadyHasBufferItem) { alreadyHasBufferItem = true; + InterpolateState.LastRemainingTime = InterpolateState.FinalTimeToTarget; InterpolateState.TargetReached = false; - startTime = InterpolateState.Target.Value.TimeSent; InterpolateState.MaxDeltaTime = maxDeltaTime; - InterpolateState.PredictingNext = m_BufferQueue.Count > 0; - if (lerpAhead) - { - InterpolateState.Phase1Value = InterpolateState.PreviousValue; - if (InterpolateState.PredictingNext && m_BufferQueue.TryPeek(out BufferedItem nextTarget)) - { - InterpolateState.Phase2Value = Interpolate(InterpolateState.PredictValue, nextTarget.Item, InterpolateState.CurrentDeltaTime); - } - else - { - InterpolateState.Phase2Value = InterpolateState.PredictValue; - } - } + InterpolateState.PreviousValue = InterpolateState.NextValue; + startTime = InterpolateState.Target.Value.TimeSent; } InterpolateState.SetTimeToTarget(Math.Max(target.TimeSent - startTime, minDeltaTime)); InterpolateState.Target = target; @@ -388,7 +346,6 @@ internal void ResetCurrentState() { InterpolateState.Reset(InterpolateState.CurrentValue); m_RateOfChange = default; - m_PredictedRateOfChange = default; } } @@ -403,11 +360,11 @@ internal void ResetCurrentState() /// The tick latency in relative local time. /// The minimum time delta between the current and target value. /// The maximum time delta between the current and target value. - /// Determines whether to use smooth dampening or lerp ahead interpolation type. + /// Determines whether to use smooth dampening or lerp interpolation type. /// The newly interpolated value of type 'T' - internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime, double maxDeltaTime, bool lerpAhead) + internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime, double maxDeltaTime, bool lerp) { - TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime, lerpAhead); + TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime); // Only begin interpolation when there is a start and end point if (InterpolateState.Target.HasValue) { @@ -417,34 +374,26 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime // Increases the time delta relative to the time to target. // Also calculates the LerpT and LerpTPredicted values. InterpolateState.AddDeltaTime(deltaTime); - var targetValue = InterpolateState.CurrentValue; // SmoothDampen - if (!lerpAhead) + if (!lerp) { - InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.TimeToTargetValue * InterpolateState.LerpT); - InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)(InterpolateState.TimeToTargetValue * InterpolateState.LerpTPredict)); + InterpolateState.NextValue = SmoothDamp(InterpolateState.NextValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue * InterpolateState.LerpT, deltaTime); } - else// Lerp Ahead + else// Lerp { - InterpolateState.PreviousValue = Interpolate(InterpolateState.Phase1Value, InterpolateState.Target.Value.Item, InterpolateState.LerpT); - // Note: InterpolateState.LerpTPredict is clamped to LerpT if we have no next target - InterpolateState.PredictValue = InterpolateUnclamped(InterpolateState.Phase2Value, InterpolateState.Target.Value.Item, InterpolateState.LerpTPredict); - + InterpolateState.NextValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); } - // Lerp between the PreviousValue and PredictedValue using the current delta time - targetValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime); - // If lerp smoothing is enabled, then smooth current value towards the target value if (LerpSmoothEnabled) { // Apply the smooth lerp to the target to help smooth the final value. - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, targetValue, deltaTime / MaximumInterpolationTime); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, Mathf.Clamp(1.0f - MaximumInterpolationTime, 0.0f, 1.0f)); } else { // Otherwise, just assign the target value. - InterpolateState.CurrentValue = targetValue; + InterpolateState.CurrentValue = InterpolateState.NextValue; } } else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. @@ -499,7 +448,7 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) { InterpolateState.Target = target; alreadyHasBufferItem = true; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.NextValue = InterpolateState.CurrentValue; InterpolateState.StartTime = target.TimeSent; InterpolateState.EndTime = target.TimeSent; } @@ -509,7 +458,7 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) { alreadyHasBufferItem = true; InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.NextValue = InterpolateState.CurrentValue; InterpolateState.TargetReached = false; } InterpolateState.EndTime = target.TimeSent; @@ -550,12 +499,13 @@ public T Update(float deltaTime, double renderTime, double serverTime) { // The original BufferedLinearInterpolator lerping script to assure the Smooth Dampening updates do not impact // this specific behavior. - float t = 1.0f; + InterpolateState.LerpT = 1.0f; if (InterpolateState.TimeToTargetValue > k_SmallValue) { - t = Math.Clamp((float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue), 0.0f, 1.0f); + InterpolateState.LerpT = Math.Clamp((float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue), 0.0f, 1.0f); } - var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, t); + + var target = Interpolate(InterpolateState.NextValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); if (LerpSmoothEnabled) { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 58afb854b2..aeb9687e7b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -962,15 +962,14 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade public enum InterpolationTypes { /// - /// Lerp (original NGO lerping model)
- /// Uses a 1 to 2 phase interpolation approach where:
+ /// Legacy Lerp (original NGO lerping model)
+ /// Uses a 1 to 2 phase lerp approach where:
/// /// The first phase lerps from the previous state update value to the next state update value. /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of delta time divided by the respective max interpolation time. /// ///
/// - /// Lowest computation cost of the three options.
/// For more information:
/// /// @@ -981,20 +980,16 @@ public enum InterpolationTypes /// /// ///
- Lerp, + LegacyLerp, /// - /// Lerp Ahead - /// Uses a 3 to 4 phase approach that lerps towards the target, lerps towards the next target (if one exists) ahead by 1 frame delta, blends the two results, and (optionally) smooth the final value.
+ /// Lerp (maintains time to target when under higher conditions)
+ /// Uses a 1 to 2 phase interpolation approach where:
/// - /// The first phase lerps towards the current tick state update being processed. - /// The second phase lerps unclamped (extrapolates) towards the next tick state update. If there is no next target, then it lerps towards the current target unclamped with "t" being clamped to 1.0. - /// The third phase lerps between the results of the first and second phases by the current delta time. - /// The fourth phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. + /// The first phase lerps from the previous state update value to the next state update value. + /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of 1.0 minus the respective maximum interpolation time. /// ///
/// - /// Note: This is slightly more computationally expensive than the approach.
- /// It is recommended to turn lerp smoothing off or adjust the interpolation time to a lower value if you want a more precise end result.
/// For more information:
/// /// @@ -1005,19 +1000,18 @@ public enum InterpolationTypes /// /// ///
- LerpAhead, + Lerp, /// - /// Uses a 3 to 4 phase approach that smooth dampens towards the target, smooth dampens ahead of the target, blends the two results, and (optionally) smooth the final value.
+ /// Smooth Dampening (maintains time to target when under higher conditions)
+ /// Uses a 1 to 2 phase smooth dampening approach where:
/// /// The first phase smooth dampens towards the current tick state update being processed by the accumulated delta time relative to the time to target. - /// The second phase smooth dampens ahead of the previous phase by the accumulated delta time, relative to the time to target, plus the current delta time for the current update. - /// The third phase lerps between the results of the first and second phases by the current delta time. - /// The fourth phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. + /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the third phase at a rate of delta time divided by the respective max interpolation time. /// ///
/// - /// Note: Smooth dampening is computationally more expensive than the and approaches.
- /// It is recommended to turn lerp smoothing off or adjust the interpolation time to a lower value if you want a more precise end result. + /// Note: Smooth dampening is computationally more expensive than the and approaches.
+ /// It is recommended to turn lerp smoothing off or adjust the maximum interpolation time to a lower value if you want a more precise end result. /// For more information:
/// /// @@ -1036,9 +1030,9 @@ public enum InterpolationTypes ///
/// /// - /// Yields a traditional linear result. - /// Lerps, extrapolates, and blends the results. - /// Adjusts based on the rate of change. + /// Yields the original Netcode for GameObjects lerp result. + /// Uses the newer linear buffer queue consumption approach that maintains a consistent time to the next target. + /// Uses the newer linear buffer queue consumption approach and adjusts based on the rate of change. /// /// Things to consider:
/// @@ -1055,9 +1049,9 @@ public enum InterpolationTypes /// /// /// - /// Yields a traditional linear result. - /// Lerps, extrapolates, and blends the results. - /// Adjusts based on the rate of change. + /// Yields the original Netcode for GameObjects lerp result. + /// Uses the newer linear buffer queue consumption approach that maintains a consistent time to the next target. + /// Uses the newer linear buffer queue consumption approach and adjusts based on the rate of change. /// /// Things to consider:
/// @@ -1074,9 +1068,9 @@ public enum InterpolationTypes /// /// /// - /// Yields a traditional linear result. - /// Lerps, extrapolates, and blends the results. - /// Adjusts based on the rate of change. + /// Yields the original Netcode for GameObjects lerp result. + /// Uses the newer linear buffer queue consumption approach that maintains a consistent time to the next target. + /// Uses the newer linear buffer queue consumption approach and adjusts based on the rate of change. /// /// Things to consider:
/// @@ -1098,19 +1092,13 @@ public enum InterpolationTypes private bool m_PreviousPositionLerpSmoothing; /// - /// The position interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp.
+ /// The position interoplation maximum interpolation time.
/// /// The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). /// The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value). /// This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. linear velocity or the like). /// ///
- /// - /// - /// Only used When is enabled. - /// The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) - /// - /// [Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] public float PositionMaxInterpolationTime = 0.1f; @@ -1125,19 +1113,13 @@ public enum InterpolationTypes private bool m_PreviousRotationLerpSmoothing; /// - /// The rotation interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. + /// The rotation interoplation maximum interpolation time.
/// /// The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). /// The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value). /// This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. angular velocity). /// ///
- /// - /// - /// Only used When is enabled. - /// The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) - /// - /// [Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] public float RotationMaxInterpolationTime = 0.1f; @@ -1152,19 +1134,13 @@ public enum InterpolationTypes private bool m_PreviousScaleLerpSmoothing; /// - /// The scale interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. + /// The scale interoplation maximum interpolation time.
/// /// The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). /// The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value). /// This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value. /// ///
- /// - /// - /// Only used When is enabled. - /// The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) - /// - /// [Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] public float ScaleMaxInterpolationTime = 0.1f; @@ -4023,14 +3999,12 @@ public struct NTPositionStats public int BufferCount; public int TickMeasured; public float LerpT; - public float LerpTPredict; public double DeltaTime; - public double DeltaTimePredict; public double MaxDeltaTime; public double TimeSent; public double TimeToTargetValue; public Vector3 PreviousValue; - public Vector3 PredictValue; + public Vector3 NextValue; public Vector3 CurrentValue; public Vector3 TargetValue; @@ -4065,6 +4039,16 @@ public double GetPositionCurrentStateTimeToTarget() { return m_PositionInterpolator.InterpolateState.TimeToTargetValue; } + + public double GetPositionLerpT() + { + return m_PositionInterpolator.InterpolateState.LerpT; + } + + public double GetPositionLastRemainingTime() + { + return m_PositionInterpolator.InterpolateState.LastRemainingTime; + } #endif @@ -4141,14 +4125,14 @@ private void UpdateInterpolation() m_PositionInterpolator.ResetCurrentState(); } - if (PositionInterpolationType == InterpolationTypes.Lerp) + if (PositionInterpolationType == InterpolationTypes.LegacyLerp) { m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); } else { m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, - PositionInterpolationType == InterpolationTypes.LerpAhead); + PositionInterpolationType == InterpolationTypes.Lerp); } } @@ -4172,14 +4156,14 @@ private void UpdateInterpolation() // When using full precision Slerp towards the target rotation. /// m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; - if (RotationInterpolationType == InterpolationTypes.Lerp) + if (RotationInterpolationType == InterpolationTypes.LegacyLerp) { m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); } else { m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, - RotationInterpolationType == InterpolationTypes.LerpAhead); + RotationInterpolationType == InterpolationTypes.Lerp); } } @@ -4200,14 +4184,14 @@ private void UpdateInterpolation() m_ScaleInterpolator.ResetCurrentState(); } - if (ScaleInterpolationType == InterpolationTypes.Lerp) + if (ScaleInterpolationType == InterpolationTypes.LegacyLerp) { m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); } else { m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime, - ScaleInterpolationType == InterpolationTypes.LerpAhead); + ScaleInterpolationType == InterpolationTypes.Lerp); } } @@ -4231,14 +4215,12 @@ private void UpdateInterpolation() BufferCount = m_PositionInterpolator.m_BufferQueue.Count, TickMeasured = (int)Math.Round(m_PositionInterpolator.InterpolateState.Target.Value.TimeSent / timeSystem.FixedDeltaTimeAsDouble, MidpointRounding.AwayFromZero), LerpT = m_PositionInterpolator.InterpolateState.LerpT, - LerpTPredict = m_PositionInterpolator.InterpolateState.LerpTPredict, DeltaTime = m_PositionInterpolator.InterpolateState.DeltaTime, - DeltaTimePredict = m_PositionInterpolator.InterpolateState.DeltaTimePredict, MaxDeltaTime = m_PositionInterpolator.InterpolateState.MaxDeltaTime, TimeSent = m_PositionInterpolator.InterpolateState.Target.Value.TimeSent, TimeToTargetValue = m_PositionInterpolator.InterpolateState.TimeToTargetValue, PreviousValue = m_PositionInterpolator.InterpolateState.PreviousValue, - PredictValue = m_PositionInterpolator.InterpolateState.PredictValue, + NextValue = m_PositionInterpolator.InterpolateState.NextValue, CurrentValue = m_PositionInterpolator.InterpolateState.CurrentValue, TargetValue = m_PositionInterpolator.InterpolateState.Target.Value.Item, Buffer = new List(), diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 56cfacdfcb..92c47522c1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -4,12 +4,12 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.LerpAhead)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.Lerp)] internal class NetworkTransformGeneral : NetworkTransformBase { public enum SmoothLerpSettings diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index e3b4d95884..d4f22c11c5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -10,22 +10,22 @@ namespace Unity.Netcode.RuntimeTests /// server and host operating modes and will test both authoritative /// models for each operating mode. /// + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] @@ -33,44 +33,44 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] #endif + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] #endif + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LegacyLerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.LerpAhead)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] diff --git a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs index 4713a7861b..dc5b2b4e9d 100644 --- a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs @@ -36,18 +36,18 @@ namespace TestProject.RuntimeTests [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] // Lerp, extrapolate, and blend pass - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] - [TestFixture(NetworkTransform.InterpolationTypes.LerpAhead, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.Lerp, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] // Smooth dampening interpolation pass [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] @@ -121,7 +121,7 @@ public NestedNetworkTransformTests(NetworkTransform.InterpolationTypes interpola } public NestedNetworkTransformTests(Interpolation interpolation, Precision precision, NetworkTransform.AuthorityModes authoritativeModel, NestedTickSynchronization nestedTickSynchronization) : - this(NetworkTransform.InterpolationTypes.Lerp, interpolation, precision, authoritativeModel, nestedTickSynchronization) + this(NetworkTransform.InterpolationTypes.LegacyLerp, interpolation, precision, authoritativeModel, nestedTickSynchronization) { } public NestedNetworkTransformTests() From 8b67753b71e89df2c563ff6378c2d8feaf121c3a Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 3 Apr 2025 01:00:32 -0500 Subject: [PATCH 50/50] update updating changelog entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index b12b6c755e..74718c95b9 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -11,7 +11,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added - Added `NetworkManager.OnPreShutdown` which is called before the NetworkManager cleans up and shuts down. (#3366) -- Added `LerpAhead` interpolation type that provides users with something between standard lerp and smooth dampening. (#3355) +- Added `Lerp` interpolation type that still uses a lerp approach but uses the new buffer consumption logic. (#3355) - Added property to enable or disable lerp smoothing for position, rotation, and scale interpolators. (#3355) - Added `NetworkTransform.InterpolationBufferTickOffset` static property to provide users with a way to increase or decrease the time marker where interpolators will pull state update from the queue. (#3355) - Added interpolator types as an inspector view selection for position, rotation, and scale. (#3337) @@ -43,6 +43,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed the original `Lerp` interpolation type to `LegacyLerp`. (#3355) - Changed `BufferedLinearInterpolator.Update(float deltaTime, NetworkTime serverTime)` as being deprecated since this method is only used for internal testing purposes. (#3337) - Changed error thrown when attempting to build a dedicated server with Unity Transport that uses websockets to provide more useful information to the user. (#3336) - Changed root in-scene placed `NetworkObject` instances now will always have either the `Distributable` permission set unless the `SessionOwner` permission is set. (#3305)