44
55namespace 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