Skip to content

Commit 4e9174c

Browse files
fix
This update resolves the issue where the legacy lerp result was not yielding the same result as the original lerp. This update also resolves a secondary issue discovered by the services group where the final interpolation (i.e. last measurement in the queue) was not using a high enough precision value when determining if it reached its final destination point. This update also includes some inlining additions along with a spelling mistake with the IsApproximately method. It also updates the XML API regarding Legacy lerp to let users know that it does not use the tick latency value when calculating the ticksAgo value.
1 parent cad4b4c commit 4e9174c

File tree

5 files changed

+56
-23
lines changed

5 files changed

+56
-23
lines changed

com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Runtime.CompilerServices;
34
using UnityEngine;
45

56
namespace Unity.Netcode
@@ -14,9 +15,16 @@ public abstract class BufferedLinearInterpolator<T> where T : struct
1415
// Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so
1516
// that we don't have a very small buffer because of this.
1617
private const int k_BufferCountLimit = 100;
17-
private const float k_AproximatePrecision = 0.0001f;
18+
private const float k_ApproximateLowPrecision = 0.000001f;
19+
private const float k_ApproximateHighPrecision = 1E-10f;
1820
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
1921

22+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
23+
private float GetPrecision()
24+
{
25+
return m_BufferQueue.Count == 0 ? k_ApproximateHighPrecision : k_ApproximateLowPrecision;
26+
}
27+
2028
#region Legacy notes
2129
// Buffer consumption scenarios
2230
// Perfect case consumption
@@ -132,20 +140,23 @@ internal struct CurrentState
132140
public float CurrentDeltaTime => m_CurrentDeltaTime;
133141
public double FinalTimeToTarget => Math.Max(0.0, TimeToTargetValue - DeltaTime);
134142

143+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
135144
public void AddDeltaTime(float deltaTime)
136145
{
137146
m_CurrentDeltaTime = deltaTime;
138147
DeltaTime = Math.Min(DeltaTime + deltaTime, TimeToTargetValue);
139148
LerpT = (float)(TimeToTargetValue == 0.0 ? 1.0 : DeltaTime / TimeToTargetValue);
140149
}
141150

151+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
142152
public void SetTimeToTarget(double timeToTarget)
143153
{
144154
LerpT = 0.0f;
145155
DeltaTime = 0.0f;
146156
TimeToTargetValue = timeToTarget;
147157
}
148158

159+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
149160
public bool TargetTimeAproximatelyReached()
150161
{
151162
if (!Target.HasValue)
@@ -188,6 +199,7 @@ public void Reset(T currentValue)
188199
/// The current buffered items received by the authority.
189200
/// </summary>
190201
protected internal readonly Queue<BufferedItem> m_BufferQueue = new Queue<BufferedItem>(k_BufferCountLimit);
202+
protected internal readonly List<BufferedItem> m_BufferList = new List<BufferedItem>(k_BufferCountLimit);
191203

192204
/// <summary>
193205
/// The current interpolation state
@@ -237,6 +249,7 @@ public void ResetTo(T targetValue, double serverTime)
237249
InternalReset(targetValue, serverTime);
238250
}
239251

252+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
240253
private void InternalReset(T targetValue, double serverTime, bool addMeasurement = true)
241254
{
242255
m_RateOfChange = default;
@@ -271,7 +284,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double
271284
{
272285
if (!InterpolateState.TargetReached)
273286
{
274-
InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item);
287+
InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision());
275288
}
276289
return;
277290
}
@@ -291,7 +304,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double
291304
potentialItemNeedsProcessing = ((potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent);
292305
if (!InterpolateState.TargetReached)
293306
{
294-
InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item);
307+
InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision());
295308
}
296309
}
297310

@@ -424,23 +437,27 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime
424437
/// This version of TryConsumeFromBuffer adheres to the original BufferedLinearInterpolator buffer consumption pattern.
425438
/// </remarks>
426439
/// <param name="renderTime"></param>
440+
/// <param name="serverTime"></param>
427441
private void TryConsumeFromBuffer(double renderTime, double serverTime)
428442
{
429443
if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime))
430444
{
431445
BufferedItem? previousItem = null;
432446
var alreadyHasBufferItem = false;
447+
var count = 0;
433448
while (m_BufferQueue.TryPeek(out BufferedItem potentialItem))
434449
{
435450
// If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing
436-
// to consume.
451+
// to consume. (just a safety check but this scenario should never happen based on the below legacy approach of
452+
// consuming until the most current state)
437453
if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent)
438454
{
439455
break;
440456
}
441457

442-
if ((potentialItem.TimeSent <= serverTime) &&
443-
(!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent))
458+
// Continue to processing until we reach the most current state
459+
if ((potentialItem.TimeSent <= serverTime) && // Inverted logic (below) from original since we have to go from past to present
460+
(!InterpolateState.Target.HasValue || potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent))
444461
{
445462
if (m_BufferQueue.TryDequeue(out BufferedItem target))
446463
{
@@ -449,6 +466,7 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime)
449466
InterpolateState.Target = target;
450467
alreadyHasBufferItem = true;
451468
InterpolateState.NextValue = InterpolateState.CurrentValue;
469+
InterpolateState.PreviousValue = InterpolateState.CurrentValue;
452470
InterpolateState.StartTime = target.TimeSent;
453471
InterpolateState.EndTime = target.TimeSent;
454472
}
@@ -458,19 +476,15 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime)
458476
{
459477
alreadyHasBufferItem = true;
460478
InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent;
461-
InterpolateState.NextValue = InterpolateState.CurrentValue;
479+
InterpolateState.PreviousValue = InterpolateState.NextValue;
462480
InterpolateState.TargetReached = false;
463481
}
464482
InterpolateState.EndTime = target.TimeSent;
465-
InterpolateState.Target = target;
466483
InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime;
484+
InterpolateState.Target = target;
467485
}
468486
}
469487
}
470-
else
471-
{
472-
break;
473-
}
474488

475489
if (!InterpolateState.Target.HasValue)
476490
{
@@ -505,19 +519,20 @@ public T Update(float deltaTime, double renderTime, double serverTime)
505519
InterpolateState.LerpT = Math.Clamp((float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue), 0.0f, 1.0f);
506520
}
507521

508-
var target = Interpolate(InterpolateState.NextValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT);
522+
InterpolateState.NextValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT);
509523

510524
if (LerpSmoothEnabled)
511525
{
512526
// Assure our MaximumInterpolationTime is valid and that the second lerp time ranges between deltaTime and 1.0f.
513-
InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, deltaTime / MaximumInterpolationTime);
527+
InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, deltaTime / MaximumInterpolationTime);
514528
}
515529
else
516530
{
517-
InterpolateState.CurrentValue = target;
531+
InterpolateState.CurrentValue = InterpolateState.NextValue;
518532
}
533+
519534
// Determine if we have reached our target
520-
InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item);
535+
InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision());
521536
}
522537
else // If the target is reached and we have no more state updates, we want to check to see if we need to reset.
523538
if (m_BufferQueue.Count == 0 && InterpolateState.TargetReached)
@@ -601,6 +616,7 @@ public void AddMeasurement(T newMeasurement, double sentTime)
601616
/// Gets latest value from the interpolator. This is updated every update as time goes by.
602617
/// </summary>
603618
/// <returns>The current interpolated value of type 'T'</returns>
619+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
604620
public T GetInterpolatedValue()
605621
{
606622
return InterpolateState.CurrentValue;
@@ -638,6 +654,7 @@ public T GetInterpolatedValue()
638654
/// <param name="deltaTime">The increasing delta time from when start to finish.</param>
639655
/// <param name="maxSpeed">Maximum rate of change per pass.</param>
640656
/// <returns>The smoothed <see cref="T"/> value.</returns>
657+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
641658
private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange, float duration, float deltaTime, float maxSpeed = Mathf.Infinity)
642659
{
643660
return target;
@@ -653,7 +670,8 @@ private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange,
653670
/// <param name="second">Second value of type <see cref="T"/>.</param>
654671
/// <param name="precision">The precision of the aproximation.</param>
655672
/// <returns>true if the two values are aproximately the same and false if they are not</returns>
656-
private protected virtual bool IsAproximately(T first, T second, float precision = k_AproximatePrecision)
673+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
674+
private protected virtual bool IsApproximately(T first, T second, float precision = k_ApproximateLowPrecision)
657675
{
658676
return false;
659677
}
@@ -665,6 +683,7 @@ private protected virtual bool IsAproximately(T first, T second, float precision
665683
/// <param name="item">The item to convert.</param>
666684
/// <param name="inLocalSpace">local or world space (true or false).</param>
667685
/// <returns>The converted value.</returns>
686+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
668687
protected internal virtual T OnConvertTransformSpace(Transform transform, T item, bool inLocalSpace)
669688
{
670689
return default;
@@ -675,6 +694,7 @@ protected internal virtual T OnConvertTransformSpace(Transform transform, T item
675694
/// </summary>
676695
/// <param name="transform">The transform that the <see cref="Components.NetworkTransform"/> is associated with.</param>
677696
/// <param name="inLocalSpace">Whether the <see cref="Components.NetworkTransform"/> is now being tracked in local or world spaced.</param>
697+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
678698
internal void ConvertTransformSpace(Transform transform, bool inLocalSpace)
679699
{
680700
var count = m_BufferQueue.Count;

com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protected override float Interpolate(float start, float end, float time)
2525

2626
/// <inheritdoc />
2727
[MethodImpl(MethodImplOptions.AggressiveInlining)]
28-
private protected override bool IsAproximately(float first, float second, float precision = 1E-07F)
28+
private protected override bool IsApproximately(float first, float second, float precision = 1E-06F)
2929
{
3030
return Mathf.Approximately(first, second);
3131
}

com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ private protected override Quaternion SmoothDamp(Quaternion current, Quaternion
6666

6767
/// <inheritdoc />
6868
[MethodImpl(MethodImplOptions.AggressiveInlining)]
69-
private protected override bool IsAproximately(Quaternion first, Quaternion second, float precision)
69+
private protected override bool IsApproximately(Quaternion first, Quaternion second, float precision = 1E-06F)
7070
{
7171
return Mathf.Abs(first.x - second.x) <= precision &&
7272
Mathf.Abs(first.y - second.y) <= precision &&

com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ protected internal override Vector3 OnConvertTransformSpace(Transform transform,
5959

6060
/// <inheritdoc />
6161
[MethodImpl(MethodImplOptions.AggressiveInlining)]
62-
private protected override bool IsAproximately(Vector3 first, Vector3 second, float precision = 0.0001F)
62+
private protected override bool IsApproximately(Vector3 first, Vector3 second, float precision = 1E-06F)
6363
{
6464
return Math.Round(Mathf.Abs(first.x - second.x), 2) <= precision &&
6565
Math.Round(Mathf.Abs(first.y - second.y), 2) <= precision &&

com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,8 @@ public enum InterpolationTypes
968968
/// <item><description>The first phase lerps from the previous state update value to the next state update value.</description></item>
969969
/// <item><description>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.</description></item>
970970
/// </list>
971+
/// !!! NOTE !!!<br />
972+
/// The legacy lerp interpolation type does not use <see cref="NetworkTimeSystem.TickLatency"/> to determine the buffer depth. This is to preserve the same interpolation results when lerp smoothing is enabled.<br />
971973
/// </summary>
972974
/// <remarks>
973975
/// For more information:<br />
@@ -4085,6 +4087,17 @@ private void UpdateInterpolation()
40854087
}
40864088
}
40874089

4090+
// 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
4091+
// project's to match the legacy lerp's end result. This will not allow changes
4092+
var cachedRenderTime = 0.0;
4093+
if (PositionInterpolationType == InterpolationTypes.LegacyLerp || RotationInterpolationType == InterpolationTypes.LegacyLerp || ScaleInterpolationType == InterpolationTypes.LegacyLerp)
4094+
{
4095+
// Since InterpolationBufferTickOffset defaults to zero, this should not impact exist projects but still provides users with the ability to tweak
4096+
// their ticks ago time.
4097+
var ticksAgo = (!IsServerAuthoritative() && !IsServer ? 2 : 1) + InterpolationBufferTickOffset;
4098+
cachedRenderTime = timeSystem.TimeTicksAgo(ticksAgo).Time;
4099+
}
4100+
40884101
// Get the tick latency (ticks ago) as time (in the past) to process state updates in the queue.
40894102
var tickLatencyAsTime = timeSystem.TimeTicksAgo(tickLatency).Time;
40904103

@@ -4127,7 +4140,7 @@ private void UpdateInterpolation()
41274140

41284141
if (PositionInterpolationType == InterpolationTypes.LegacyLerp)
41294142
{
4130-
m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime);
4143+
m_PositionInterpolator.Update(cachedDeltaTime, cachedRenderTime, currentTime);
41314144
}
41324145
else
41334146
{
@@ -4158,7 +4171,7 @@ private void UpdateInterpolation()
41584171
m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision;
41594172
if (RotationInterpolationType == InterpolationTypes.LegacyLerp)
41604173
{
4161-
m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime);
4174+
m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, currentTime);
41624175
}
41634176
else
41644177
{
@@ -4186,7 +4199,7 @@ private void UpdateInterpolation()
41864199

41874200
if (ScaleInterpolationType == InterpolationTypes.LegacyLerp)
41884201
{
4189-
m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, currentTime);
4202+
m_ScaleInterpolator.Update(cachedDeltaTime, cachedRenderTime, currentTime);
41904203
}
41914204
else
41924205
{

0 commit comments

Comments
 (0)