diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 538a6ba1a1..b07eca8250 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode @@ -14,9 +15,16 @@ public abstract class BufferedLinearInterpolator where T : struct // Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so // that we don't have a very small buffer because of this. private const int k_BufferCountLimit = 100; - private const float k_AproximatePrecision = 0.0001f; + private const float k_ApproximateLowPrecision = 0.000001f; + private const float k_ApproximateHighPrecision = 1E-10f; private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float GetPrecision() + { + return m_BufferQueue.Count == 0 ? k_ApproximateHighPrecision : k_ApproximateLowPrecision; + } + #region Legacy notes // Buffer consumption scenarios // Perfect case consumption @@ -132,6 +140,7 @@ internal struct CurrentState public float CurrentDeltaTime => m_CurrentDeltaTime; public double FinalTimeToTarget => Math.Max(0.0, TimeToTargetValue - DeltaTime); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddDeltaTime(float deltaTime) { m_CurrentDeltaTime = deltaTime; @@ -139,6 +148,7 @@ public void AddDeltaTime(float deltaTime) LerpT = (float)(TimeToTargetValue == 0.0 ? 1.0 : DeltaTime / TimeToTargetValue); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetTimeToTarget(double timeToTarget) { LerpT = 0.0f; @@ -146,6 +156,7 @@ public void SetTimeToTarget(double timeToTarget) TimeToTargetValue = timeToTarget; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TargetTimeAproximatelyReached() { if (!Target.HasValue) @@ -237,6 +248,7 @@ public void ResetTo(T targetValue, double serverTime) InternalReset(targetValue, serverTime); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void InternalReset(T targetValue, double serverTime, bool addMeasurement = true) { m_RateOfChange = default; @@ -271,7 +283,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double { if (!InterpolateState.TargetReached) { - InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); + InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision()); } return; } @@ -291,7 +303,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double potentialItemNeedsProcessing = ((potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent); if (!InterpolateState.TargetReached) { - InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); + InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision()); } } @@ -424,6 +436,7 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime /// This version of TryConsumeFromBuffer adheres to the original BufferedLinearInterpolator buffer consumption pattern. /// /// + /// private void TryConsumeFromBuffer(double renderTime, double serverTime) { if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime)) @@ -433,14 +446,16 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) { // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing - // to consume. + // to consume. (just a safety check but this scenario should never happen based on the below legacy approach of + // consuming until the most current state) if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) { break; } - if ((potentialItem.TimeSent <= serverTime) && - (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) + // Continue to processing until we reach the most current state + if ((potentialItem.TimeSent <= serverTime) && // Inverted logic (below) from original since we have to go from past to present + (!InterpolateState.Target.HasValue || potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent)) { if (m_BufferQueue.TryDequeue(out BufferedItem target)) { @@ -449,6 +464,7 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) InterpolateState.Target = target; alreadyHasBufferItem = true; InterpolateState.NextValue = InterpolateState.CurrentValue; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; InterpolateState.StartTime = target.TimeSent; InterpolateState.EndTime = target.TimeSent; } @@ -458,19 +474,15 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) { alreadyHasBufferItem = true; InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent; - InterpolateState.NextValue = InterpolateState.CurrentValue; + InterpolateState.PreviousValue = InterpolateState.NextValue; InterpolateState.TargetReached = false; } InterpolateState.EndTime = target.TimeSent; - InterpolateState.Target = target; InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime; + InterpolateState.Target = target; } } } - else - { - break; - } if (!InterpolateState.Target.HasValue) { @@ -505,19 +517,20 @@ public T Update(float deltaTime, double renderTime, double serverTime) InterpolateState.LerpT = Math.Clamp((float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue), 0.0f, 1.0f); } - var target = Interpolate(InterpolateState.NextValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); + InterpolateState.NextValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); if (LerpSmoothEnabled) { // Assure our MaximumInterpolationTime is valid and that the second lerp time ranges between deltaTime and 1.0f. - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, deltaTime / MaximumInterpolationTime); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, deltaTime / MaximumInterpolationTime); } else { - InterpolateState.CurrentValue = target; + InterpolateState.CurrentValue = InterpolateState.NextValue; } + // Determine if we have reached our target - InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); + InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision()); } else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. if (m_BufferQueue.Count == 0 && InterpolateState.TargetReached) @@ -601,6 +614,7 @@ public void AddMeasurement(T newMeasurement, double sentTime) /// Gets latest value from the interpolator. This is updated every update as time goes by. /// /// The current interpolated value of type 'T' + [MethodImpl(MethodImplOptions.AggressiveInlining)] public T GetInterpolatedValue() { return InterpolateState.CurrentValue; @@ -638,6 +652,7 @@ public T GetInterpolatedValue() /// The increasing delta time from when start to finish. /// Maximum rate of change per pass. /// The smoothed value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange, float duration, float deltaTime, float maxSpeed = Mathf.Infinity) { return target; @@ -653,7 +668,8 @@ private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange, /// Second value of type . /// The precision of the aproximation. /// true if the two values are aproximately the same and false if they are not - private protected virtual bool IsAproximately(T first, T second, float precision = k_AproximatePrecision) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private protected virtual bool IsApproximately(T first, T second, float precision = k_ApproximateLowPrecision) { return false; } @@ -665,6 +681,7 @@ private protected virtual bool IsAproximately(T first, T second, float precision /// The item to convert. /// local or world space (true or false). /// The converted value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected internal virtual T OnConvertTransformSpace(Transform transform, T item, bool inLocalSpace) { return default; @@ -675,6 +692,7 @@ protected internal virtual T OnConvertTransformSpace(Transform transform, T item /// /// The transform that the is associated with. /// Whether the is now being tracked in local or world spaced. + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) { var count = m_BufferQueue.Count; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs index 77583468a7..654d64917a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs @@ -25,7 +25,7 @@ protected override float Interpolate(float start, float end, float time) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected override bool IsAproximately(float first, float second, float precision = 1E-07F) + private protected override bool IsApproximately(float first, float second, float precision = 1E-06F) { return Mathf.Approximately(first, second); } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs index 5b8033a977..628498ada0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs @@ -66,7 +66,7 @@ private protected override Quaternion SmoothDamp(Quaternion current, Quaternion /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected override bool IsAproximately(Quaternion first, Quaternion second, float precision) + private protected override bool IsApproximately(Quaternion first, Quaternion second, float precision = 1E-06F) { return Mathf.Abs(first.x - second.x) <= precision && Mathf.Abs(first.y - second.y) <= precision && diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs index aa5a739683..ce836bc199 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs @@ -59,7 +59,7 @@ protected internal override Vector3 OnConvertTransformSpace(Transform transform, /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected override bool IsAproximately(Vector3 first, Vector3 second, float precision = 0.0001F) + private protected override bool IsApproximately(Vector3 first, Vector3 second, float precision = 1E-06F) { return Math.Round(Mathf.Abs(first.x - second.x), 2) <= precision && Math.Round(Mathf.Abs(first.y - second.y), 2) <= precision && diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index aeb9687e7b..92943ee0b5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -968,6 +968,8 @@ public enum InterpolationTypes /// The first phase lerps from the previous state update value to the next state update value. /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of delta time divided by the respective max interpolation time. /// + /// !!! NOTE !!!
+ /// The legacy lerp interpolation type does not use to determine the buffer depth. This is to preserve the same interpolation results when lerp smoothing is enabled.
/// /// /// For more information:
@@ -4085,6 +4087,17 @@ private void UpdateInterpolation() } } + // Note: This is for the legacy lerp type in order to maintain the same end result for any games under development that have tuned their + // project's to match the legacy lerp's end result. This will not allow changes + var cachedRenderTime = 0.0; + if (PositionInterpolationType == InterpolationTypes.LegacyLerp || RotationInterpolationType == InterpolationTypes.LegacyLerp || ScaleInterpolationType == InterpolationTypes.LegacyLerp) + { + // Since InterpolationBufferTickOffset defaults to zero, this should not impact exist projects but still provides users with the ability to tweak + // their ticks ago time. + var ticksAgo = (!IsServerAuthoritative() && !IsServer ? 2 : 1) + InterpolationBufferTickOffset; + cachedRenderTime = timeSystem.TimeTicksAgo(ticksAgo).Time; + } + // Get the tick latency (ticks ago) as time (in the past) to process state updates in the queue. var tickLatencyAsTime = timeSystem.TimeTicksAgo(tickLatency).Time; @@ -4127,7 +4140,7 @@ private void UpdateInterpolation() if (PositionInterpolationType == InterpolationTypes.LegacyLerp) { - m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); + m_PositionInterpolator.Update(cachedDeltaTime, cachedRenderTime, currentTime); } else { @@ -4158,7 +4171,7 @@ private void UpdateInterpolation() m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; if (RotationInterpolationType == InterpolationTypes.LegacyLerp) { - m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); + m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, currentTime); } else { @@ -4186,7 +4199,7 @@ private void UpdateInterpolation() if (ScaleInterpolationType == InterpolationTypes.LegacyLerp) { - m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime); + m_ScaleInterpolator.Update(cachedDeltaTime, cachedRenderTime, currentTime); } else {