Skip to content

Commit 096e1ba

Browse files
perf!: network transform performance improvements (#1283)
* perf checkpoint * simplified component change check, cleaned up one-tick-behind check
1 parent 3f298fd commit 096e1ba

File tree

8 files changed

+286
-287
lines changed

8 files changed

+286
-287
lines changed

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

Lines changed: 50 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,62 +4,35 @@
44

55
namespace Unity.Netcode
66
{
7+
78
/// <summary>
89
/// Solves for incoming values that are jittered
910
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
1011
/// </summary>
1112
/// <typeparam name="T"></typeparam>
1213
internal abstract class BufferedLinearInterpolator<T> where T : struct
1314
{
14-
// interface for mock testing, abstracting away external systems
15-
internal interface IInterpolatorTime
16-
{
17-
double BufferedServerTime { get; }
18-
double BufferedServerFixedTime { get; }
19-
uint TickRate { get; }
20-
}
21-
22-
private class InterpolatorTime : IInterpolatorTime
23-
{
24-
private readonly NetworkManager m_Manager;
25-
public InterpolatorTime(NetworkManager manager)
26-
{
27-
m_Manager = manager;
28-
}
29-
public double BufferedServerTime => m_Manager.ServerTime.Time;
30-
public double BufferedServerFixedTime => m_Manager.ServerTime.FixedTime;
31-
public uint TickRate => m_Manager.ServerTime.TickRate;
32-
}
33-
34-
internal IInterpolatorTime InterpolatorTimeProxy;
35-
3615
private struct BufferedItem
3716
{
3817
public T Item;
39-
public NetworkTime TimeSent;
18+
public double TimeSent;
4019

41-
public BufferedItem(T item, NetworkTime timeSent)
20+
public BufferedItem(T item, double timeSent)
4221
{
4322
Item = item;
4423
TimeSent = timeSent;
4524
}
4625
}
4726

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

50-
/// <summary>
51-
/// Override this if you want configurable buffering, right now using ServerTick's own global buffering
52-
/// </summary>
53-
private double ServerTimeBeingHandledForBuffering => InterpolatorTimeProxy.BufferedServerTime;
54-
55-
private double RenderTime => InterpolatorTimeProxy.BufferedServerTime - 1f / InterpolatorTimeProxy.TickRate;
28+
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
5629

5730
private T m_InterpStartValue;
5831
private T m_CurrentInterpValue;
5932
private T m_InterpEndValue;
6033

61-
private NetworkTime m_EndTimeConsumed;
62-
private NetworkTime m_StartTimeConsumed;
34+
private double m_EndTimeConsumed;
35+
private double m_StartTimeConsumed;
6336

6437
private readonly List<BufferedItem> m_Buffer = new List<BufferedItem>(k_BufferCountLimit);
6538

@@ -96,47 +69,44 @@ public BufferedItem(T item, NetworkTime timeSent)
9669

9770
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
9871

99-
internal BufferedLinearInterpolator(NetworkManager manager)
100-
{
101-
InterpolatorTimeProxy = new InterpolatorTime(manager);
102-
}
103-
104-
internal BufferedLinearInterpolator(IInterpolatorTime proxy)
105-
{
106-
InterpolatorTimeProxy = proxy;
107-
}
108-
109-
public void ResetTo(T targetValue)
72+
public void ResetTo(T targetValue, double serverTime)
11073
{
11174
m_LifetimeConsumedCount = 1;
11275
m_InterpStartValue = targetValue;
11376
m_InterpEndValue = targetValue;
11477
m_CurrentInterpValue = targetValue;
11578
m_Buffer.Clear();
116-
m_EndTimeConsumed = new NetworkTime(InterpolatorTimeProxy.TickRate, 0);
117-
m_StartTimeConsumed = new NetworkTime(InterpolatorTimeProxy.TickRate, 0);
79+
m_EndTimeConsumed = 0.0d;
80+
m_StartTimeConsumed = 0.0d;
11881

119-
Update(0);
82+
Update(0, serverTime, serverTime);
12083
}
12184

12285

12386
// 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
124-
private void TryConsumeFromBuffer()
87+
private void TryConsumeFromBuffer(double renderTime, double serverTime)
12588
{
12689
int consumedCount = 0;
12790
// only consume if we're ready
128-
if (RenderTime >= m_EndTimeConsumed.Time)
91+
92+
// this operation was measured as one of our most expensive, and we should put some thought into this.
93+
// NetworkTransform has (currently) 7 buffered linear interpolators (3 position, 3 scale, 1 rot), and
94+
// each has its own independent buffer and 'm_endTimeConsume'. That means every frame I have to do 7x
95+
// these checks vs. if we tracked these values in a unified way
96+
if (renderTime >= m_EndTimeConsumed)
12997
{
13098
BufferedItem? itemToInterpolateTo = null;
13199
// assumes we're using sequenced messages for netvar syncing
132100
// buffer contains oldest values first, iterating from end to start to remove elements from list while iterating
101+
102+
// calling m_Buffer.Count shows up hot in the profiler.
133103
for (int i = m_Buffer.Count - 1; i >= 0; i--) // todo stretch: consume ahead if we see we're missing values due to packet loss
134104
{
135105
var bufferedValue = m_Buffer[i];
136106
// Consume when ready and interpolate to last value we can consume. This can consume multiple values from the buffer
137-
if (bufferedValue.TimeSent.Time <= ServerTimeBeingHandledForBuffering)
107+
if (bufferedValue.TimeSent <= serverTime)
138108
{
139-
if (!itemToInterpolateTo.HasValue || bufferedValue.TimeSent.Time > itemToInterpolateTo.Value.TimeSent.Time)
109+
if (!itemToInterpolateTo.HasValue || bufferedValue.TimeSent > itemToInterpolateTo.Value.TimeSent)
140110
{
141111
if (m_LifetimeConsumedCount == 0)
142112
{
@@ -151,7 +121,7 @@ private void TryConsumeFromBuffer()
151121
m_InterpStartValue = m_InterpEndValue;
152122
}
153123

154-
if (bufferedValue.TimeSent.Time > m_EndTimeConsumed.Time)
124+
if (bufferedValue.TimeSent > m_EndTimeConsumed)
155125
{
156126
itemToInterpolateTo = bufferedValue;
157127
m_EndTimeConsumed = bufferedValue.TimeSent;
@@ -167,9 +137,27 @@ private void TryConsumeFromBuffer()
167137
}
168138
}
169139

170-
public T Update(float deltaTime)
140+
/// <summary>
141+
/// Convenience version of 'Update' mainly for testing
142+
/// the reason we don't want to always call this version is so that on the calling side we can compute
143+
/// the renderTime once for the many things being interpolated (and the many interpolators per object)
144+
/// </summary>
145+
/// <param name="deltaTime">time since call</param>
146+
/// <param name="serverTime">current server time</param>
147+
public T Update(float deltaTime, NetworkTime serverTime)
148+
{
149+
return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time);
150+
}
151+
152+
/// <summary>
153+
/// Call to update the state of the interpolators before reading out
154+
/// </summary>
155+
/// <param name="deltaTime">time since last call</param>
156+
/// <param name="renderTime">our current time</param>
157+
/// <param name="serverTime">current server time</param>
158+
public T Update(float deltaTime, double renderTime, double serverTime)
171159
{
172-
TryConsumeFromBuffer();
160+
TryConsumeFromBuffer(renderTime, serverTime);
173161

174162
if (InvalidState)
175163
{
@@ -184,14 +172,14 @@ public T Update(float deltaTime)
184172
if (m_LifetimeConsumedCount >= 1) // shouldn't interpolate between default values, let's wait to receive data first, should only interpolate between real measurements
185173
{
186174
float t = 1.0f;
187-
double range = m_EndTimeConsumed.Time - m_StartTimeConsumed.Time;
175+
double range = m_EndTimeConsumed - m_StartTimeConsumed;
188176
if (range > k_SmallValue)
189177
{
190-
t = (float)((RenderTime - m_StartTimeConsumed.Time) / range);
178+
t = (float)((renderTime - m_StartTimeConsumed) / range);
191179

192180
if (t < 0.0f)
193181
{
194-
throw new OverflowException($"t = {t} but must be >= 0. range {range}, RenderTime {RenderTime}, Start time {m_StartTimeConsumed.Time}, end time {m_EndTimeConsumed.Time}");
182+
throw new OverflowException($"t = {t} but must be >= 0. range {range}, RenderTime {renderTime}, Start time {m_StartTimeConsumed}, end time {m_EndTimeConsumed}");
195183
}
196184

197185
if (t > 3.0f) // max extrapolation
@@ -211,24 +199,24 @@ public T Update(float deltaTime)
211199
return m_CurrentInterpValue;
212200
}
213201

214-
public void AddMeasurement(T newMeasurement, NetworkTime sentTime)
202+
public void AddMeasurement(T newMeasurement, double sentTime)
215203
{
216204
m_NbItemsReceivedThisFrame++;
217205

218206
// This situation can happen after a game is paused. When starting to receive again, the server will have sent a bunch of messages in the meantime
219207
// instead of going through thousands of value updates just to get a big teleport, we're giving up on interpolation and teleporting to the latest value
220208
if (m_NbItemsReceivedThisFrame > k_BufferCountLimit)
221209
{
222-
if (m_LastBufferedItemReceived.TimeSent.Time < sentTime.Time)
210+
if (m_LastBufferedItemReceived.TimeSent < sentTime)
223211
{
224212
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
225-
ResetTo(newMeasurement);
213+
ResetTo(newMeasurement, sentTime);
226214
}
227215

228216
return;
229217
}
230218

231-
if (sentTime.Time > m_EndTimeConsumed.Time || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now
219+
if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now
232220
{
233221
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
234222
m_Buffer.Add(m_LastBufferedItemReceived);
@@ -244,6 +232,7 @@ public T GetInterpolatedValue()
244232
protected abstract T InterpolateUnclamped(T start, T end, float time);
245233
}
246234

235+
247236
internal class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
248237
{
249238
protected override float InterpolateUnclamped(float start, float end, float time)
@@ -255,14 +244,6 @@ protected override float Interpolate(float start, float end, float time)
255244
{
256245
return Mathf.Lerp(start, end, time);
257246
}
258-
259-
public BufferedLinearInterpolatorFloat(NetworkManager manager) : base(manager)
260-
{
261-
}
262-
263-
public BufferedLinearInterpolatorFloat(IInterpolatorTime proxy) : base(proxy)
264-
{
265-
}
266247
}
267248

268249
internal class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
@@ -276,9 +257,5 @@ protected override Quaternion Interpolate(Quaternion start, Quaternion end, floa
276257
{
277258
return Quaternion.SlerpUnclamped(start, end, time);
278259
}
279-
280-
public BufferedLinearInterpolatorQuaternion(NetworkManager manager) : base(manager)
281-
{
282-
}
283260
}
284261
}

0 commit comments

Comments
 (0)