Skip to content

Commit 6eadea1

Browse files
update
Some additional updates... still having a hard time getting a single lerp pass without some form of slight jitter. This includes a temporary 2nd pass lerp that still allows for the buffered item value to be completely reached.
1 parent e2ae8eb commit 6eadea1

2 files changed

Lines changed: 155 additions & 78 deletions

File tree

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

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

56
namespace Unity.Netcode
@@ -11,7 +12,7 @@ namespace Unity.Netcode
1112
/// <typeparam name="T">The type of interpolated value</typeparam>
1213
public abstract class BufferedLinearInterpolator<T> where T : struct
1314
{
14-
internal float MaxInterpolationBound = 3.0f;
15+
internal float MaxInterpolationBound = 1.0f;
1516
protected internal struct BufferedItem
1617
{
1718
public T Item;
@@ -31,14 +32,7 @@ public BufferedItem(T item, double timeSent)
3132

3233
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
3334

34-
protected internal T m_InterpStartValue;
35-
protected internal T m_CurrentInterpValue;
36-
protected internal T m_InterpEndValue;
37-
38-
private double m_EndTimeConsumed;
39-
private double m_StartTimeConsumed;
40-
41-
protected internal readonly List<BufferedItem> m_Buffer = new List<BufferedItem>(k_BufferCountLimit);
35+
protected internal readonly Queue<BufferedItem> m_Buffer = new Queue<BufferedItem>(k_BufferCountLimit);
4236

4337

4438

@@ -71,6 +65,7 @@ public BufferedItem(T item, double timeSent)
7165
private BufferedItem m_LastBufferedItemReceived;
7266
private int m_NbItemsReceivedThisFrame;
7367

68+
protected internal T m_CurrentInterpValue;
7469
private int m_LifetimeConsumedCount;
7570

7671
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
@@ -96,8 +91,12 @@ internal void ConvertTransformSpace(Transform transform, bool inLocalSpace)
9691
public void Clear()
9792
{
9893
m_Buffer.Clear();
99-
m_EndTimeConsumed = 0.0d;
100-
m_StartTimeConsumed = 0.0d;
94+
m_CurrentInterpValue = default;
95+
InterpolateState = new CurrentState()
96+
{
97+
CurrentValue = default,
98+
LerpT = 0.0000001f,
99+
};
101100
}
102101

103102
/// <summary>
@@ -108,18 +107,20 @@ public void Clear()
108107
public void ResetTo(T targetValue, double serverTime)
109108
{
110109
m_LifetimeConsumedCount = 1;
111-
m_InterpStartValue = targetValue;
112-
m_InterpEndValue = targetValue;
113-
m_CurrentInterpValue = targetValue;
114110
m_Buffer.Clear();
115-
m_EndTimeConsumed = 0.0d;
116-
m_StartTimeConsumed = 0.0d;
117-
111+
m_Name = GetType().Name;
112+
m_CurrentInterpValue = targetValue;
113+
InterpolateState = new CurrentState()
114+
{
115+
CurrentValue = targetValue,
116+
LerpT = 0.0000001f,
117+
};
118118
Update(0, serverTime, serverTime);
119119
}
120120

121121
// todo if I have value 1, 2, 3 and I'm treating 1 to 3, I shouldn't interpolate between 1 and 3, I should interpolate from 1 to 2, then from 2 to 3 to get the best path
122-
private void TryConsumeFromBuffer(double renderTime, double serverTime)
122+
#if OLDSTUFF
123+
private void TryConsumeFromBufferLDK(double renderTime, double serverTime)
123124
{
124125
int consumedCount = 0;
125126
// only consume if we're ready
@@ -152,15 +153,21 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime)
152153
else if (consumedCount == 0)
153154
{
154155
// Interpolating to new value, end becomes start. We then look in our buffer for a new end.
155-
m_StartTimeConsumed = m_EndTimeConsumed;
156-
m_InterpStartValue = m_InterpEndValue;
156+
157+
// !!!! This does not account for gaps between values !!!
158+
// if last entry is > several ticks then the range is going to be very large!
159+
m_StartTimeConsumed = renderTime;
160+
m_InterpStartValue = m_CurrentInterpValue;
157161
}
158162

159-
if (bufferedValue.TimeSent > m_EndTimeConsumed)
163+
if ((bufferedValue.TimeSent - m_StartTimeConsumed) >= k_TickFrequency)
160164
{
161165
itemToInterpolateTo = bufferedValue;
162166
m_EndTimeConsumed = bufferedValue.TimeSent;
163167
m_InterpEndValue = bufferedValue.Item;
168+
m_Buffer.RemoveAt(i);
169+
m_LifetimeConsumedCount++;
170+
break;
164171
}
165172
}
166173

@@ -171,6 +178,80 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime)
171178
}
172179
}
173180
}
181+
#endif
182+
private string m_Name;
183+
184+
internal struct CurrentState
185+
{
186+
public BufferedItem? Start;
187+
188+
public BufferedItem? End;
189+
190+
public double RelativeTime;
191+
192+
public T CurrentValue;
193+
194+
public float LerpT;
195+
}
196+
197+
internal CurrentState InterpolateState;
198+
199+
private void TryConsumeFromBuffer(double renderTime, double serverTime)
200+
{
201+
// If we don't have our initial buffered item/starting point or our end point or the end point's time sent is less than the
202+
// render time
203+
if (!InterpolateState.Start.HasValue || !InterpolateState.End.HasValue || InterpolateState.End.Value.TimeSent < renderTime)
204+
{
205+
BufferedItem? previousItem = null;
206+
while (m_Buffer.TryPeek(out BufferedItem potentialItem))
207+
{
208+
if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent)
209+
{
210+
break;
211+
}
212+
213+
if (potentialItem.TimeSent <= serverTime)
214+
{
215+
// We want to initialize and then always set the end
216+
if (!InterpolateState.Start.HasValue)
217+
{
218+
if (m_Buffer.TryDequeue(out BufferedItem start))
219+
{
220+
InterpolateState.Start = start;
221+
InterpolateState.RelativeTime = InterpolateState.Start.Value.TimeSent;
222+
InterpolateState.CurrentValue = start.Item;
223+
InterpolateState.LerpT = 0.0f;
224+
InterpolateState.Start = start;
225+
}
226+
}
227+
else if (!InterpolateState.End.HasValue || InterpolateState.End.Value.TimeSent < potentialItem.TimeSent)
228+
{
229+
if (m_Buffer.TryDequeue(out BufferedItem end))
230+
{
231+
if (InterpolateState.End.HasValue)
232+
{
233+
InterpolateState.Start = InterpolateState.End;
234+
//m_CurrentState.RelativeTime = m_CurrentState.End.Value.TimeSent;
235+
}
236+
InterpolateState.End = end;
237+
InterpolateState.LerpT = 0.0f;
238+
m_LifetimeConsumedCount++;
239+
break;
240+
}
241+
}
242+
else
243+
{
244+
break;
245+
}
246+
}
247+
if (!InterpolateState.Start.HasValue)
248+
{
249+
break;
250+
}
251+
previousItem = potentialItem;
252+
}
253+
}
254+
}
174255

175256
/// <summary>
176257
/// Convenience version of 'Update' mainly for testing
@@ -201,48 +282,33 @@ public T Update(float deltaTime, double renderTime, double serverTime)
201282
throw new InvalidOperationException("trying to update interpolator when no data has been added to it yet");
202283
}
203284

204-
// Interpolation example to understand the math below
205-
// 4 4.5 6 6.5
206-
// | | | |
207-
// A render B Server
208-
209-
if (m_LifetimeConsumedCount >= 1) // shouldn't interpolate between default values, let's wait to receive data first, should only interpolate between real measurements
285+
// Only interpolate when there is a start and end point and we have not already reached the end value
286+
if (InterpolateState.Start.HasValue && InterpolateState.End.HasValue)
210287
{
211-
float t = 1.0f;
212-
double range = m_EndTimeConsumed - m_StartTimeConsumed;
213-
if (range > k_SmallValue)
288+
if (InterpolateState.LerpT < 1.0f)
214289
{
215-
var rangeFactor = 1.0f / (float)range;
216-
217-
t = ((float)renderTime - (float)m_StartTimeConsumed) * rangeFactor;
218-
219-
if (t < 0.0f)
290+
InterpolateState.RelativeTime = Math.Clamp(InterpolateState.RelativeTime + deltaTime, 0.000001f, InterpolateState.End.Value.TimeSent);
291+
//var t = 1.0f - Mathf.Clamp((float)((m_EndTimeConsumed - renderTime) * rangeFactor), 0.0f, 1.0f);
292+
//var alt_t = 1.0f - Mathf.Clamp((float)((renderTime - m_StartTimeConsumed) * rangeFactor), 0.0f, 1.0f);
293+
InterpolateState.LerpT = (float)(InterpolateState.RelativeTime / InterpolateState.End.Value.TimeSent);
294+
InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.End.Value.Item, InterpolateState.LerpT);
295+
if (InterpolateState.LerpT < 1.0f)
220296
{
221-
// There is no mechanism to guarantee renderTime to not be before m_StartTimeConsumed
222-
// This clamps t to a minimum of 0 and fixes issues with longer frames and pauses
223-
224-
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
225-
{
226-
NetworkLog.LogError($"renderTime was before m_StartTimeConsumed. This should never happen. {nameof(renderTime)} is {renderTime}, {nameof(m_StartTimeConsumed)} is {m_StartTimeConsumed}");
227-
}
228-
t = 0.0f;
297+
m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, InterpolateState.CurrentValue, 0.5f);
229298
}
230-
231-
if (t > MaxInterpolationBound) // max extrapolation
299+
else
232300
{
233-
// TODO this causes issues with teleport, investigate
234-
t = 1.0f;
301+
m_CurrentInterpValue = InterpolateState.CurrentValue;
235302
}
236303
}
237-
238-
var target = InterpolateUnclamped(m_InterpStartValue, m_InterpEndValue, t);
239-
m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / MaximumInterpolationTime); // second interpolate to smooth out extrapolation jumps
240304
}
241305

242306
m_NbItemsReceivedThisFrame = 0;
243307
return m_CurrentInterpValue;
244308
}
245309

310+
311+
private double m_LastSentTime = 0.0f;
246312
/// <summary>
247313
/// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value".
248314
/// </summary>
@@ -261,18 +327,23 @@ public void AddMeasurement(T newMeasurement, double sentTime)
261327
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
262328
ResetTo(newMeasurement, sentTime);
263329
// Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues
264-
m_Buffer.Add(m_LastBufferedItemReceived);
330+
m_Buffer.Enqueue(m_LastBufferedItemReceived);
265331
}
266332

267333
return;
268334
}
269335

270336
// Part the of reason for disabling extrapolation is how we add and use measurements over time.
271337
// TODO: Add detailed description of this area in Jira ticket
272-
if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now
338+
if (sentTime > m_LastSentTime || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now
273339
{
274340
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
275-
m_Buffer.Add(m_LastBufferedItemReceived);
341+
m_Buffer.Enqueue(m_LastBufferedItemReceived);
342+
m_LastSentTime = sentTime;
343+
}
344+
else
345+
{
346+
Debug.Log($"[{m_Name}] Dropping measurement -- Time: {sentTime} Value: {newMeasurement}");
276347
}
277348
}
278349

@@ -315,7 +386,7 @@ protected override float InterpolateUnclamped(float start, float end, float time
315386
{
316387
// Disabling Extrapolation:
317388
// TODO: Add Jira Ticket
318-
return Mathf.Lerp(start, end, time);
389+
return Mathf.LerpUnclamped(start, end, time);
319390
}
320391

321392
/// <inheritdoc />
@@ -348,11 +419,11 @@ protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion
348419
{
349420
if (IsSlerp)
350421
{
351-
return Quaternion.Slerp(start, end, time);
422+
return Quaternion.SlerpUnclamped(start, end, time);
352423
}
353424
else
354425
{
355-
return Quaternion.Lerp(start, end, time);
426+
return Quaternion.LerpUnclamped(start, end, time);
356427
}
357428
}
358429

@@ -384,16 +455,19 @@ private Quaternion ConvertToNewTransformSpace(Transform transform, Quaternion ro
384455

385456
protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace)
386457
{
387-
for (int i = 0; i < m_Buffer.Count; i++)
458+
var buffer = m_Buffer.ToList();
459+
m_Buffer.Clear();
460+
for (int i = 0; i < buffer.Count; i++)
388461
{
389-
var entry = m_Buffer[i];
462+
var entry = buffer[i];
390463
entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace);
391-
m_Buffer[i] = entry;
464+
m_Buffer.Enqueue(entry);
392465
}
393-
394-
m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace);
466+
InterpolateState.CurrentValue = ConvertToNewTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace);
395467
m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace);
396-
m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace);
468+
var end = InterpolateState.End.Value;
469+
end.Item = ConvertToNewTransformSpace(transform, end.Item, inLocalSpace);
470+
InterpolateState.End = end;
397471

398472
base.OnConvertTransformSpace(transform, inLocalSpace);
399473
}
@@ -414,11 +488,11 @@ protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, floa
414488
{
415489
if (IsSlerp)
416490
{
417-
return Vector3.Slerp(start, end, time);
491+
return Vector3.SlerpUnclamped(start, end, time);
418492
}
419493
else
420494
{
421-
return Vector3.Lerp(start, end, time);
495+
return Vector3.LerpUnclamped(start, end, time);
422496
}
423497
}
424498

@@ -450,16 +524,20 @@ private Vector3 ConvertToNewTransformSpace(Transform transform, Vector3 position
450524

451525
protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace)
452526
{
453-
for (int i = 0; i < m_Buffer.Count; i++)
527+
var buffer = m_Buffer.ToList();
528+
m_Buffer.Clear();
529+
for (int i = 0; i < buffer.Count; i++)
454530
{
455-
var entry = m_Buffer[i];
531+
var entry = buffer[i];
456532
entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace);
457-
m_Buffer[i] = entry;
533+
m_Buffer.Enqueue(entry);
458534
}
459535

460-
m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace);
536+
InterpolateState.CurrentValue = ConvertToNewTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace);
461537
m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace);
462-
m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace);
538+
var end = InterpolateState.End.Value;
539+
end.Item = ConvertToNewTransformSpace(transform, end.Item, inLocalSpace);
540+
InterpolateState.End = end;
463541

464542
base.OnConvertTransformSpace(transform, inLocalSpace);
465543
}

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,11 +1785,11 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
17851785
var positionThreshold = Vector3.one * PositionThreshold;
17861786
var rotationThreshold = Vector3.one * RotAngleThreshold;
17871787

1788-
if (m_UseRigidbodyForMotion)
1789-
{
1790-
positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold();
1791-
rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold();
1792-
}
1788+
//if (m_UseRigidbodyForMotion)
1789+
//{
1790+
// positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold();
1791+
// rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold();
1792+
//}
17931793
#else
17941794
var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position;
17951795
var rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation;
@@ -3704,10 +3704,9 @@ private void UpdateInterpolation()
37043704
// is to make their cachedRenderTime run 2 ticks behind.
37053705

37063706
// TODO: This could most likely just always be 2
3707-
// var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1;
3708-
var ticksAgo = 2;
3709-
3710-
var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time;
3707+
//var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1;
3708+
//var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time;
3709+
var cachedRenderTime = serverTime.TimeTicksAgo(2).Time;
37113710

37123711
// Now only update the interpolators for the portions of the transform being synchronized
37133712
if (SynchronizePosition)

0 commit comments

Comments
 (0)