4
4
5
5
namespace Unity . Netcode
6
6
{
7
+
7
8
/// <summary>
8
9
/// Solves for incoming values that are jittered
9
10
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
10
11
/// </summary>
11
12
/// <typeparam name="T"></typeparam>
12
13
internal abstract class BufferedLinearInterpolator < T > where T : struct
13
14
{
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
-
36
15
private struct BufferedItem
37
16
{
38
17
public T Item ;
39
- public NetworkTime TimeSent ;
18
+ public double TimeSent ;
40
19
41
- public BufferedItem ( T item , NetworkTime timeSent )
20
+ public BufferedItem ( T item , double timeSent )
42
21
{
43
22
Item = item ;
44
23
TimeSent = timeSent ;
45
24
}
46
25
}
47
26
48
- private const double k_SmallValue = 9.999999439624929E-11 ; // copied from Vector3's equal operator
49
27
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
56
29
57
30
private T m_InterpStartValue ;
58
31
private T m_CurrentInterpValue ;
59
32
private T m_InterpEndValue ;
60
33
61
- private NetworkTime m_EndTimeConsumed ;
62
- private NetworkTime m_StartTimeConsumed ;
34
+ private double m_EndTimeConsumed ;
35
+ private double m_StartTimeConsumed ;
63
36
64
37
private readonly List < BufferedItem > m_Buffer = new List < BufferedItem > ( k_BufferCountLimit ) ;
65
38
@@ -96,47 +69,44 @@ public BufferedItem(T item, NetworkTime timeSent)
96
69
97
70
private bool InvalidState => m_Buffer . Count == 0 && m_LifetimeConsumedCount == 0 ;
98
71
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 )
110
73
{
111
74
m_LifetimeConsumedCount = 1 ;
112
75
m_InterpStartValue = targetValue ;
113
76
m_InterpEndValue = targetValue ;
114
77
m_CurrentInterpValue = targetValue ;
115
78
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 ;
118
81
119
- Update ( 0 ) ;
82
+ Update ( 0 , serverTime , serverTime ) ;
120
83
}
121
84
122
85
123
86
// 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 )
125
88
{
126
89
int consumedCount = 0 ;
127
90
// 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 )
129
97
{
130
98
BufferedItem ? itemToInterpolateTo = null ;
131
99
// assumes we're using sequenced messages for netvar syncing
132
100
// 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.
133
103
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
134
104
{
135
105
var bufferedValue = m_Buffer [ i ] ;
136
106
// 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 )
138
108
{
139
- if ( ! itemToInterpolateTo . HasValue || bufferedValue . TimeSent . Time > itemToInterpolateTo . Value . TimeSent . Time )
109
+ if ( ! itemToInterpolateTo . HasValue || bufferedValue . TimeSent > itemToInterpolateTo . Value . TimeSent )
140
110
{
141
111
if ( m_LifetimeConsumedCount == 0 )
142
112
{
@@ -151,7 +121,7 @@ private void TryConsumeFromBuffer()
151
121
m_InterpStartValue = m_InterpEndValue ;
152
122
}
153
123
154
- if ( bufferedValue . TimeSent . Time > m_EndTimeConsumed . Time )
124
+ if ( bufferedValue . TimeSent > m_EndTimeConsumed )
155
125
{
156
126
itemToInterpolateTo = bufferedValue ;
157
127
m_EndTimeConsumed = bufferedValue . TimeSent ;
@@ -167,9 +137,27 @@ private void TryConsumeFromBuffer()
167
137
}
168
138
}
169
139
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 )
171
159
{
172
- TryConsumeFromBuffer ( ) ;
160
+ TryConsumeFromBuffer ( renderTime , serverTime ) ;
173
161
174
162
if ( InvalidState )
175
163
{
@@ -184,14 +172,14 @@ public T Update(float deltaTime)
184
172
if ( m_LifetimeConsumedCount >= 1 ) // shouldn't interpolate between default values, let's wait to receive data first, should only interpolate between real measurements
185
173
{
186
174
float t = 1.0f ;
187
- double range = m_EndTimeConsumed . Time - m_StartTimeConsumed . Time ;
175
+ double range = m_EndTimeConsumed - m_StartTimeConsumed ;
188
176
if ( range > k_SmallValue )
189
177
{
190
- t = ( float ) ( ( RenderTime - m_StartTimeConsumed . Time ) / range ) ;
178
+ t = ( float ) ( ( renderTime - m_StartTimeConsumed ) / range ) ;
191
179
192
180
if ( t < 0.0f )
193
181
{
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 } ") ;
195
183
}
196
184
197
185
if ( t > 3.0f ) // max extrapolation
@@ -211,24 +199,24 @@ public T Update(float deltaTime)
211
199
return m_CurrentInterpValue ;
212
200
}
213
201
214
- public void AddMeasurement ( T newMeasurement , NetworkTime sentTime )
202
+ public void AddMeasurement ( T newMeasurement , double sentTime )
215
203
{
216
204
m_NbItemsReceivedThisFrame ++ ;
217
205
218
206
// 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
219
207
// 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
220
208
if ( m_NbItemsReceivedThisFrame > k_BufferCountLimit )
221
209
{
222
- if ( m_LastBufferedItemReceived . TimeSent . Time < sentTime . Time )
210
+ if ( m_LastBufferedItemReceived . TimeSent < sentTime )
223
211
{
224
212
m_LastBufferedItemReceived = new BufferedItem ( newMeasurement , sentTime ) ;
225
- ResetTo ( newMeasurement ) ;
213
+ ResetTo ( newMeasurement , sentTime ) ;
226
214
}
227
215
228
216
return ;
229
217
}
230
218
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
232
220
{
233
221
m_LastBufferedItemReceived = new BufferedItem ( newMeasurement , sentTime ) ;
234
222
m_Buffer . Add ( m_LastBufferedItemReceived ) ;
@@ -244,6 +232,7 @@ public T GetInterpolatedValue()
244
232
protected abstract T InterpolateUnclamped ( T start , T end , float time ) ;
245
233
}
246
234
235
+
247
236
internal class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator < float >
248
237
{
249
238
protected override float InterpolateUnclamped ( float start , float end , float time )
@@ -255,14 +244,6 @@ protected override float Interpolate(float start, float end, float time)
255
244
{
256
245
return Mathf . Lerp ( start , end , time ) ;
257
246
}
258
-
259
- public BufferedLinearInterpolatorFloat ( NetworkManager manager ) : base ( manager )
260
- {
261
- }
262
-
263
- public BufferedLinearInterpolatorFloat ( IInterpolatorTime proxy ) : base ( proxy )
264
- {
265
- }
266
247
}
267
248
268
249
internal class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator < Quaternion >
@@ -276,9 +257,5 @@ protected override Quaternion Interpolate(Quaternion start, Quaternion end, floa
276
257
{
277
258
return Quaternion . SlerpUnclamped ( start , end , time ) ;
278
259
}
279
-
280
- public BufferedLinearInterpolatorQuaternion ( NetworkManager manager ) : base ( manager )
281
- {
282
- }
283
260
}
284
261
}
0 commit comments