Skip to content

Commit bb0566c

Browse files
fix: LegacyLerp was using ticklatency as opposed to legacy ticksAgo (#3381)
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. ## Changelog NA ## Testing and Documentation - No tests have been added. - Includes edits to existing API XML documentation. <!-- Uncomment and mark items off with a * if this PR deprecates any API: ### Deprecated API - [ ] An `[Obsolete]` attribute was added along with a `(RemovedAfter yyyy-mm-dd)` entry. - [ ] An [api updater] was added. - [ ] Deprecation of the API is explained in the CHANGELOG. - [ ] The users can understand why this API was removed and what they should use instead. -->
1 parent cad4b4c commit bb0566c

File tree

5 files changed

+54
-23
lines changed

5 files changed

+54
-23
lines changed

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

Lines changed: 35 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)
@@ -237,6 +248,7 @@ public void ResetTo(T targetValue, double serverTime)
237248
InternalReset(targetValue, serverTime);
238249
}
239250

251+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
240252
private void InternalReset(T targetValue, double serverTime, bool addMeasurement = true)
241253
{
242254
m_RateOfChange = default;
@@ -271,7 +283,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double
271283
{
272284
if (!InterpolateState.TargetReached)
273285
{
274-
InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item);
286+
InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision());
275287
}
276288
return;
277289
}
@@ -291,7 +303,7 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double
291303
potentialItemNeedsProcessing = ((potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent);
292304
if (!InterpolateState.TargetReached)
293305
{
294-
InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item);
306+
InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision());
295307
}
296308
}
297309

@@ -424,6 +436,7 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime
424436
/// This version of TryConsumeFromBuffer adheres to the original BufferedLinearInterpolator buffer consumption pattern.
425437
/// </remarks>
426438
/// <param name="renderTime"></param>
439+
/// <param name="serverTime"></param>
427440
private void TryConsumeFromBuffer(double renderTime, double serverTime)
428441
{
429442
if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime))
@@ -433,14 +446,16 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime)
433446
while (m_BufferQueue.TryPeek(out BufferedItem potentialItem))
434447
{
435448
// If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing
436-
// to consume.
449+
// to consume. (just a safety check but this scenario should never happen based on the below legacy approach of
450+
// consuming until the most current state)
437451
if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent)
438452
{
439453
break;
440454
}
441455

442-
if ((potentialItem.TimeSent <= serverTime) &&
443-
(!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent))
456+
// Continue to processing until we reach the most current state
457+
if ((potentialItem.TimeSent <= serverTime) && // Inverted logic (below) from original since we have to go from past to present
458+
(!InterpolateState.Target.HasValue || potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent))
444459
{
445460
if (m_BufferQueue.TryDequeue(out BufferedItem target))
446461
{
@@ -449,6 +464,7 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime)
449464
InterpolateState.Target = target;
450465
alreadyHasBufferItem = true;
451466
InterpolateState.NextValue = InterpolateState.CurrentValue;
467+
InterpolateState.PreviousValue = InterpolateState.CurrentValue;
452468
InterpolateState.StartTime = target.TimeSent;
453469
InterpolateState.EndTime = target.TimeSent;
454470
}
@@ -458,19 +474,15 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime)
458474
{
459475
alreadyHasBufferItem = true;
460476
InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent;
461-
InterpolateState.NextValue = InterpolateState.CurrentValue;
477+
InterpolateState.PreviousValue = InterpolateState.NextValue;
462478
InterpolateState.TargetReached = false;
463479
}
464480
InterpolateState.EndTime = target.TimeSent;
465-
InterpolateState.Target = target;
466481
InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime;
482+
InterpolateState.Target = target;
467483
}
468484
}
469485
}
470-
else
471-
{
472-
break;
473-
}
474486

475487
if (!InterpolateState.Target.HasValue)
476488
{
@@ -505,19 +517,20 @@ public T Update(float deltaTime, double renderTime, double serverTime)
505517
InterpolateState.LerpT = Math.Clamp((float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue), 0.0f, 1.0f);
506518
}
507519

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

510522
if (LerpSmoothEnabled)
511523
{
512524
// 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);
525+
InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, deltaTime / MaximumInterpolationTime);
514526
}
515527
else
516528
{
517-
InterpolateState.CurrentValue = target;
529+
InterpolateState.CurrentValue = InterpolateState.NextValue;
518530
}
531+
519532
// Determine if we have reached our target
520-
InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item);
533+
InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision());
521534
}
522535
else // If the target is reached and we have no more state updates, we want to check to see if we need to reset.
523536
if (m_BufferQueue.Count == 0 && InterpolateState.TargetReached)
@@ -601,6 +614,7 @@ public void AddMeasurement(T newMeasurement, double sentTime)
601614
/// Gets latest value from the interpolator. This is updated every update as time goes by.
602615
/// </summary>
603616
/// <returns>The current interpolated value of type 'T'</returns>
617+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
604618
public T GetInterpolatedValue()
605619
{
606620
return InterpolateState.CurrentValue;
@@ -638,6 +652,7 @@ public T GetInterpolatedValue()
638652
/// <param name="deltaTime">The increasing delta time from when start to finish.</param>
639653
/// <param name="maxSpeed">Maximum rate of change per pass.</param>
640654
/// <returns>The smoothed <see cref="T"/> value.</returns>
655+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
641656
private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange, float duration, float deltaTime, float maxSpeed = Mathf.Infinity)
642657
{
643658
return target;
@@ -653,7 +668,8 @@ private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange,
653668
/// <param name="second">Second value of type <see cref="T"/>.</param>
654669
/// <param name="precision">The precision of the aproximation.</param>
655670
/// <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)
671+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
672+
private protected virtual bool IsApproximately(T first, T second, float precision = k_ApproximateLowPrecision)
657673
{
658674
return false;
659675
}
@@ -665,6 +681,7 @@ private protected virtual bool IsAproximately(T first, T second, float precision
665681
/// <param name="item">The item to convert.</param>
666682
/// <param name="inLocalSpace">local or world space (true or false).</param>
667683
/// <returns>The converted value.</returns>
684+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
668685
protected internal virtual T OnConvertTransformSpace(Transform transform, T item, bool inLocalSpace)
669686
{
670687
return default;
@@ -675,6 +692,7 @@ protected internal virtual T OnConvertTransformSpace(Transform transform, T item
675692
/// </summary>
676693
/// <param name="transform">The transform that the <see cref="Components.NetworkTransform"/> is associated with.</param>
677694
/// <param name="inLocalSpace">Whether the <see cref="Components.NetworkTransform"/> is now being tracked in local or world spaced.</param>
695+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
678696
internal void ConvertTransformSpace(Transform transform, bool inLocalSpace)
679697
{
680698
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)