Skip to content

Commit 5e7af37

Browse files
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.
1 parent 9b8bde1 commit 5e7af37

File tree

5 files changed

+137
-139
lines changed

5 files changed

+137
-139
lines changed

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

Lines changed: 88 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public abstract class BufferedLinearInterpolator<T> where T : struct
1414
// Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so
1515
// that we don't have a very small buffer because of this.
1616
private const int k_BufferCountLimit = 100;
17-
private const float k_AproximatePrecision = 0.0001f;
17+
private const float k_AproximatePrecision = 0.00001f;
1818
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
1919

2020
#region Legacy notes
@@ -277,23 +277,21 @@ private void InternalReset(T targetValue, double serverTime, bool addMeasurement
277277
}
278278
}
279279

280-
#region Smooth Dampening and Lerp Extrapolate Blend Interpolation Handling
280+
#region Smooth Dampening and Lerp Ahead Interpolation Handling
281281
/// <summary>
282-
/// TryConsumeFromBuffer: Smooth Dampening Version
282+
/// TryConsumeFromBuffer: Smooth Dampening and Lerp Ahead Version
283283
/// </summary>
284284
/// <param name="renderTime">render time: the time in "ticks ago" relative to the current tick latency.</param>
285285
/// <param name="minDeltaTime">minimum time delta (defaults to tick frequency).</param>
286286
/// <param name="maxDeltaTime">maximum time delta which defines the maximum time duration when consuming more than one item from the buffer.</param>
287-
/// <param name="extrapolateAhead">when true, the predicted target <see cref="CurrentState.Phase2Value"/> will lerp slightly ahead of the target.</param>
288-
private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double maxDeltaTime, bool extrapolateAhead)
287+
/// <param name="lerpAhead">when true, the predicted target <see cref="CurrentState.Phase2Value"/> will lerp towards the next target by the current delta.</param>
288+
private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double maxDeltaTime, bool lerpAhead)
289289
{
290290
BufferedItem? previousItem = null;
291291
var startTime = 0.0;
292292
var alreadyHasBufferItem = false;
293293
var noStateSet = !InterpolateState.Target.HasValue;
294294
var potentialItemNeedsProcessing = false;
295-
var currentTargetTimeReached = false;
296-
var dequeuedCount = 0;
297295

298296
// 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.
299297
if (!noStateSet && m_BufferQueue.Count == 0)
@@ -317,24 +315,18 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double
317315

318316
if (!noStateSet)
319317
{
320-
potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent >= InterpolateState.Target.Value.TimeSent;
321-
currentTargetTimeReached = InterpolateState.TargetTimeAproximatelyReached(potentialItemNeedsProcessing) || InterpolateState.TargetReached;
318+
potentialItemNeedsProcessing = (potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent;
322319
if (!InterpolateState.TargetReached)
323320
{
324321
InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item);
325322
}
326323
}
327324

328-
// If we haven't set a target or the potential item's time sent is less that the current target's time sent
329-
// then pull the BufferedItem from the queue. The second portion of this accounts for scenarios where there
330-
// was bad latency and the buffer has more than one item in the queue that is less than the renderTime. Under
331-
// this scenario, we just want to continue pulling items from the queue until the last item pulled from the
332-
// queue is greater than the redner time or greater than the currently targeted item's sent time.
333-
if (noStateSet || ((currentTargetTimeReached || InterpolateState.TargetReached) && potentialItemNeedsProcessing))
325+
// If we haven't set a target or we have another item that needs processing.
326+
if (noStateSet || potentialItemNeedsProcessing)
334327
{
335328
if (m_BufferQueue.TryDequeue(out BufferedItem target))
336329
{
337-
dequeuedCount++;
338330
if (!InterpolateState.Target.HasValue)
339331
{
340332
InterpolateState.Target = target;
@@ -359,15 +351,20 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double
359351
startTime = InterpolateState.Target.Value.TimeSent;
360352
InterpolateState.MaxDeltaTime = maxDeltaTime;
361353
InterpolateState.PredictingNext = m_BufferQueue.Count > 0;
362-
InterpolateState.Phase1Value = InterpolateState.PreviousValue;
363-
if (extrapolateAhead && InterpolateState.PredictingNext)
354+
if (lerpAhead)
364355
{
365-
InterpolateState.Phase2Value = InterpolateState.PredictingNext ? Interpolate(InterpolateState.PredictValue, target.Item, InterpolateState.CurrentDeltaTime)
366-
: InterpolateState.Phase2Value = InterpolateState.PredictValue;
356+
InterpolateState.Phase1Value = InterpolateState.PreviousValue;
357+
if (InterpolateState.PredictingNext && m_BufferQueue.TryPeek(out BufferedItem nextTarget))
358+
{
359+
InterpolateState.Phase2Value = Interpolate(InterpolateState.PredictValue, nextTarget.Item, InterpolateState.CurrentDeltaTime);
360+
}
361+
else
362+
{
363+
InterpolateState.Phase2Value = InterpolateState.PredictValue;
364+
}
367365
}
368366
}
369-
370-
InterpolateState.SetTimeToTarget(Math.Max((float)(target.TimeSent - startTime), minDeltaTime));
367+
InterpolateState.SetTimeToTarget(Math.Max(target.TimeSent - startTime, minDeltaTime));
371368
InterpolateState.Target = target;
372369
}
373370
}
@@ -406,11 +403,11 @@ internal void ResetCurrentState()
406403
/// <param name="tickLatencyAsTime">The tick latency in relative local time.</param>
407404
/// <param name="minDeltaTime">The minimum time delta between the current and target value.</param>
408405
/// <param name="maxDeltaTime">The maximum time delta between the current and target value.</param>
409-
/// <param name="isLerpAndExtrapolate">Determines whether to use smooth dampening or extrapolation.</param>
406+
/// <param name="lerpAhead">Determines whether to use smooth dampening or lerp ahead interpolation type.</param>
410407
/// <returns>The newly interpolated value of type 'T'</returns>
411-
internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime, double maxDeltaTime, bool isLerpAndExtrapolate)
408+
internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime, double maxDeltaTime, bool lerpAhead)
412409
{
413-
TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime, isLerpAndExtrapolate);
410+
TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime, lerpAhead);
414411
// Only begin interpolation when there is a start and end point
415412
if (InterpolateState.Target.HasValue)
416413
{
@@ -421,20 +418,21 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime
421418
// Also calculates the LerpT and LerpTPredicted values.
422419
InterpolateState.AddDeltaTime(deltaTime);
423420
var targetValue = InterpolateState.CurrentValue;
424-
// SmoothDampen or LerpExtrapolateBlend
425-
if (!isLerpAndExtrapolate)
421+
// SmoothDampen
422+
if (!lerpAhead)
426423
{
427424
InterpolateState.PreviousValue = SmoothDamp(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, (float)InterpolateState.TimeToTargetValue, (float)InterpolateState.TimeToTargetValue * InterpolateState.LerpT);
428425
InterpolateState.PredictValue = SmoothDamp(InterpolateState.PredictValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, (float)InterpolateState.TimeToTargetValue, (float)(InterpolateState.TimeToTargetValue * InterpolateState.LerpTPredict));
429426
}
430-
else
427+
else// Lerp Ahead
431428
{
432429
InterpolateState.PreviousValue = Interpolate(InterpolateState.Phase1Value, InterpolateState.Target.Value.Item, InterpolateState.LerpT);
433430
// Note: InterpolateState.LerpTPredict is clamped to LerpT if we have no next target
434431
InterpolateState.PredictValue = InterpolateUnclamped(InterpolateState.Phase2Value, InterpolateState.Target.Value.Item, InterpolateState.LerpTPredict);
432+
435433
}
436-
437-
// Lerp between the PreviousValue and PredictedValue using the calculated time delta
434+
435+
// Lerp between the PreviousValue and PredictedValue using the current delta time
438436
targetValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.PredictValue, deltaTime);
439437

440438
// 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
477475
/// This version of TryConsumeFromBuffer adheres to the original BufferedLinearInterpolator buffer consumption pattern.
478476
/// </remarks>
479477
/// <param name="renderTime"></param>
480-
/// <param name="serverTime"></param>
481-
private void TryConsumeFromBuffer(double renderTime, double serverTime)
478+
private void TryConsumeFromBuffer(double renderTime)
482479
{
483-
if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime))
480+
BufferedItem? previousItem = null;
481+
var alreadyHasBufferItem = false;
482+
while (m_BufferQueue.TryPeek(out BufferedItem potentialItem))
484483
{
485-
BufferedItem? previousItem = null;
486-
var alreadyHasBufferItem = false;
487-
while (m_BufferQueue.TryPeek(out BufferedItem potentialItem))
484+
// If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing
485+
// to consume.
486+
if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent)
488487
{
489-
// If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing
490-
// to consume.
491-
if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent)
492-
{
493-
break;
494-
}
488+
break;
489+
}
495490

496-
if ((potentialItem.TimeSent <= serverTime) &&
497-
(!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent))
491+
if ((potentialItem.TimeSent <= renderTime) &&
492+
(!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent))
493+
{
494+
if (m_BufferQueue.TryDequeue(out BufferedItem target))
498495
{
499-
if (m_BufferQueue.TryDequeue(out BufferedItem target))
496+
if (!InterpolateState.Target.HasValue)
497+
{
498+
InterpolateState.Target = target;
499+
alreadyHasBufferItem = true;
500+
InterpolateState.PreviousValue = InterpolateState.CurrentValue;
501+
InterpolateState.StartTime = target.TimeSent;
502+
InterpolateState.EndTime = target.TimeSent;
503+
InterpolateState.TargetReached = false;
504+
}
505+
else
500506
{
501-
if (!InterpolateState.Target.HasValue)
507+
if (!alreadyHasBufferItem)
502508
{
503-
InterpolateState.Target = target;
504509
alreadyHasBufferItem = true;
510+
InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent;
505511
InterpolateState.PreviousValue = InterpolateState.CurrentValue;
506-
InterpolateState.StartTime = target.TimeSent;
507-
InterpolateState.EndTime = target.TimeSent;
508-
}
509-
else
510-
{
511-
if (!alreadyHasBufferItem)
512-
{
513-
alreadyHasBufferItem = true;
514-
InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent;
515-
InterpolateState.PreviousValue = InterpolateState.CurrentValue;
516-
}
517-
InterpolateState.EndTime = target.TimeSent;
518-
InterpolateState.Target = target;
512+
InterpolateState.TargetReached = false;
519513
}
514+
InterpolateState.EndTime = target.TimeSent;
515+
InterpolateState.Target = target;
516+
InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime;
520517
}
521518
}
522-
else
523-
{
524-
break;
525-
}
519+
}
520+
else
521+
{
522+
break;
523+
}
526524

527-
if (!InterpolateState.Target.HasValue)
528-
{
529-
break;
530-
}
531-
previousItem = potentialItem;
525+
if (!InterpolateState.Target.HasValue)
526+
{
527+
break;
532528
}
529+
previousItem = potentialItem;
533530
}
534531
}
535532

@@ -542,31 +539,22 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime)
542539
/// <param name="deltaTime">time since last call</param>
543540
/// <param name="renderTime">our current time</param>
544541
/// <param name="serverTime">current server time</param>
542+
/// <remarks>
543+
/// We no longer use serverTime.
544+
/// </remarks>
545545
/// <returns>The newly interpolated value of type 'T'</returns>
546546
public T Update(float deltaTime, double renderTime, double serverTime)
547547
{
548-
TryConsumeFromBuffer(renderTime, serverTime);
548+
TryConsumeFromBuffer(renderTime);
549549
// Only interpolate when there is a start and end point and we have not already reached the end value
550-
if (InterpolateState.Target.HasValue)
550+
if (InterpolateState.Target.HasValue && !InterpolateState.TargetReached)
551551
{
552552
// The original BufferedLinearInterpolator lerping script to assure the Smooth Dampening updates do not impact
553553
// this specific behavior.
554554
float t = 1.0f;
555-
InterpolateState.TimeToTargetValue = InterpolateState.EndTime - InterpolateState.StartTime;
556555
if (InterpolateState.TimeToTargetValue > k_SmallValue)
557556
{
558-
t = (float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue);
559-
560-
if (t < 0.0f)
561-
{
562-
t = 0.0f;
563-
}
564-
565-
if (t > MaxInterpolationBound) // max extrapolation
566-
{
567-
// TODO this causes issues with teleport, investigate
568-
t = 1.0f;
569-
}
557+
t = Math.Clamp((float)((renderTime - InterpolateState.StartTime) / InterpolateState.TimeToTargetValue), 0.0f, 1.0f);
570558
}
571559
var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, t);
572560

@@ -579,6 +567,22 @@ public T Update(float deltaTime, double renderTime, double serverTime)
579567
{
580568
InterpolateState.CurrentValue = target;
581569
}
570+
// Determine if we have reached our target
571+
InterpolateState.TargetReached = IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item);
572+
}
573+
else // If the target is reached and we have no more state updates, we want to check to see if we need to reset.
574+
if (m_BufferQueue.Count == 0 && InterpolateState.TargetReached)
575+
{
576+
// When the delta between the time sent and the current tick latency time-window is greater than the max delta time
577+
// plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero),
578+
// we will want to reset the interpolator with the current known value. This prevents the next received state update's
579+
// time to be calculated against the last calculated time which if there is an extended period of time between the two
580+
// it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then
581+
// starts moving again).
582+
if ((renderTime - InterpolateState.Target.Value.TimeSent) > 0.3f) // If we haven't recevied anything within 300ms, assume we stopped motion.
583+
{
584+
InterpolateState.Reset(InterpolateState.CurrentValue);
585+
}
582586
}
583587
m_NbItemsReceivedThisFrame = 0;
584588
return InterpolateState.CurrentValue;

0 commit comments

Comments
 (0)