Skip to content

Commit dff0016

Browse files
Merge branch 'develop-3.x.x' into fix/half-float-delta-position-dither-up-port
2 parents d9c69cf + 6c0e6d2 commit dff0016

8 files changed

Lines changed: 410 additions & 22 deletions

File tree

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ Additional documentation and release notes are available at [Multiplayer Documen
1010

1111
### Added
1212

13-
1413
### Changed
1514

1615
- Changed `NetworkTransform.UseHalfFloatPrecision` to synchronize position with a resolution of approximately 1mm regardless of how far an object has travelled. Previously the resolution could degrade to approximately 3cm. This does not increase bandwidth, but projects using `NetworkTransform.UseUnreliableDeltas` will send full precision position updates more often. (#4129)
@@ -28,15 +27,17 @@ Additional documentation and release notes are available at [Multiplayer Documen
2827
### Fixed
2928

3029
- Fixed issue where scenes additively loaded before a session started were tracked as loaded on the server but had no scene handle entries, which caused `NetworkSceneManager.UnloadScene` to log an error and leave the scene registered as loaded even though it unloaded on all peers. (#4146)
30+
- Issue where `NetworkTransform` interpolated towards a point in time taken from the local clock rather than the server clock that state updates are stamped on, which starved the interpolator on clients and reduced interpolation to snapping between state updates. (#4135)
31+
- Issue where `NetworkTransform.GetTickLatencyInSeconds` returned an absolute network timestamp that grew for as long as the session ran, rather than the tick latency as a duration in seconds that it is documented to return. (#4135)
32+
- Issue where lerp smoothing was applied per frame instead of over time, which caused the `Lerp` and `SmoothDampening` interpolation types to smooth by different amounts at different frame rates. Results at 60fps are unchanged. (#4132)
33+
- Issue where setting a maximum interpolation time of 1.0 would stop a `NetworkTransform` from interpolating at all when using the `Lerp` or `SmoothDampening` interpolation types. (#4132)
3134
- Issue where objects using `NetworkTransform.UseHalfFloatPrecision` appeared to jitter on non-authority instances while they were stationary or coming to rest, even though the authority was not moving them. (#4129)
3235
- Issue with not being able to spawn initially disabled in-scene placed objects. (#4093)
3336
- Issue with pre-instantiated network prefab instances being marked as in-scene placed. Now pre-instantiated network prefabs are dynamically spawned. (#4093)
3437
- Issue where a user could spawn runtime created `NetworkObject` that has a GlobalObjectIdHash of zero. These are not valid instances and will no longer be allowed to spawn. (#4093)
3538

36-
3739
### Security
3840

39-
4041
### Obsolete
4142

4243

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

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,23 @@ public void Reset(T currentValue)
209209
internal bool LerpSmoothEnabled;
210210

211211
/// <summary>
212-
/// Determines how much smoothing will be applied to the 2nd lerp when using the <see cref="Update(float, double, double)"/> (i.e. lerping and not smooth dampening).
212+
/// The frame rate that <see cref="MaximumInterpolationTime"/> is relative to when lerp smoothing.
213+
/// </summary>
214+
private const float k_LerpSmoothReferenceFrameRate = 60.0f;
215+
216+
/// <summary>
217+
/// Keeps a <see cref="MaximumInterpolationTime"/> of 1.0f from retaining the entire delta each frame,
218+
/// which would stop the value from ever advancing towards the target.
219+
/// </summary>
220+
private const float k_MaximumLerpSmoothRetention = 0.99f;
221+
222+
/// <summary>
223+
/// Determines how much smoothing will be applied to the 2nd lerp.
213224
/// </summary>
214225
/// <remarks>
215-
/// There's two factors affecting interpolation: <br />
216-
/// - Buffering: Which can be adjusted in set in the <see cref="NetworkManager.NetworkTimeSystem"/>.<br />
217-
/// - Interpolation time: The divisor applied to delta time where the quotient is used as the lerp time.
226+
/// Higher values are smoother, lower values are more precise. The amount of smoothing applied is
227+
/// frame rate independent.<br />
228+
/// Buffering also affects interpolation and can be adjusted via <see cref="NetworkManager.NetworkTimeSystem"/>.
218229
/// </remarks>
219230
[Range(0.016f, 1.0f)]
220231
public float MaximumInterpolationTime = 0.1f;
@@ -420,6 +431,26 @@ internal void ResetCurrentState()
420431
}
421432
}
422433

434+
/// <summary>
435+
/// Calculates the frame rate independent lerp smoothing "t" for the current frame.
436+
/// </summary>
437+
/// <remarks>
438+
/// Raising the retained portion to the number of reference frames elapsed makes the smoothing rate
439+
/// a function of elapsed time rather than of how often this is called.
440+
/// </remarks>
441+
/// <param name="deltaTime">The last frame time.</param>
442+
/// <returns>The lerp smoothing time to apply for this frame.</returns>
443+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
444+
private float GetLerpSmoothTime(float deltaTime)
445+
{
446+
var retained = Mathf.Clamp01(MaximumInterpolationTime);
447+
if (retained >= 1.0f)
448+
{
449+
retained = k_MaximumLerpSmoothRetention;
450+
}
451+
return 1.0f - Mathf.Pow(retained, deltaTime * k_LerpSmoothReferenceFrameRate);
452+
}
453+
423454
/// <summary>
424455
/// Interpolation Update to use when smooth dampening is enabled on a <see cref="Components.NetworkTransform"/>.
425456
/// </summary>
@@ -459,7 +490,7 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime
459490
if (LerpSmoothEnabled)
460491
{
461492
// Apply the smooth lerp to the target to help smooth the final value.
462-
InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, Mathf.Clamp(1.0f - MaximumInterpolationTime, 0.0f, 1.0f));
493+
InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, GetLerpSmoothTime(deltaTime));
463494
}
464495
else
465496
{

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

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,7 +1136,7 @@ public enum InterpolationTypes
11361136
/// Uses a 1 to 2 phase interpolation approach where:<br />
11371137
/// <list type="bullet">
11381138
/// <item><description>The first phase lerps from the previous state update value to the next state update value.</description></item>
1139-
/// <item><description>The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of 1.0 minus the respective maximum interpolation time.</description></item>
1139+
/// <item><description>The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a frame rate independent rate determined by the respective maximum interpolation time.</description></item>
11401140
/// </list>
11411141
/// </summary>
11421142
/// <remarks>
@@ -1156,7 +1156,7 @@ public enum InterpolationTypes
11561156
/// Uses a 1 to 2 phase smooth dampening approach where:<br />
11571157
/// <list type="bullet">
11581158
/// <item><description>The first phase smooth dampens towards the current tick state update being processed by the accumulated delta time relative to the time to target.</description></item>
1159-
/// <item><description>The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of delta time divided by the respective max interpolation time.</description></item>
1159+
/// <item><description>The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a frame rate independent rate determined by the respective maximum interpolation time.</description></item>
11601160
/// </list>
11611161
/// </summary>
11621162
/// <remarks>
@@ -1236,7 +1236,11 @@ public enum InterpolationTypes
12361236
/// Controls position interpolation smoothing.
12371237
/// </summary>
12381238
/// <remarks>
1239-
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the <see cref="PositionMaxInterpolationTime"/>.
1239+
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass towards
1240+
/// the interpolated result at a rate determined by <see cref="PositionMaxInterpolationTime"/>.<br />
1241+
/// This smoothing pass is frame rate independent under <see cref="InterpolationTypes.Lerp"/> and
1242+
/// <see cref="InterpolationTypes.SmoothDampening"/>. <see cref="InterpolationTypes.LegacyLerp"/> keeps its
1243+
/// original frame rate dependent smoothing, so the same value does not produce the same result there.
12401244
/// </remarks>
12411245
public bool PositionLerpSmoothing = true;
12421246
private bool m_PreviousPositionLerpSmoothing;
@@ -1257,7 +1261,11 @@ public enum InterpolationTypes
12571261
/// Controls rotation interpolation smoothing.
12581262
/// </summary>
12591263
/// <remarks>
1260-
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the <see cref="RotationMaxInterpolationTime"/>.
1264+
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass towards
1265+
/// the interpolated result at a rate determined by <see cref="RotationMaxInterpolationTime"/>.<br />
1266+
/// This smoothing pass is frame rate independent under <see cref="InterpolationTypes.Lerp"/> and
1267+
/// <see cref="InterpolationTypes.SmoothDampening"/>. <see cref="InterpolationTypes.LegacyLerp"/> keeps its
1268+
/// original frame rate dependent smoothing, so the same value does not produce the same result there.
12611269
/// </remarks>
12621270
public bool RotationLerpSmoothing = true;
12631271
private bool m_PreviousRotationLerpSmoothing;
@@ -1278,7 +1286,11 @@ public enum InterpolationTypes
12781286
/// Controls scale interpolation smoothing.
12791287
/// </summary>
12801288
/// <remarks>
1281-
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the <see cref="ScaleMaxInterpolationTime"/>.
1289+
/// When enabled, the <see cref="BufferedLinearInterpolator{T}"/> will apply a final lerping pass towards
1290+
/// the interpolated result at a rate determined by <see cref="ScaleMaxInterpolationTime"/>.<br />
1291+
/// This smoothing pass is frame rate independent under <see cref="InterpolationTypes.Lerp"/> and
1292+
/// <see cref="InterpolationTypes.SmoothDampening"/>. <see cref="InterpolationTypes.LegacyLerp"/> keeps its
1293+
/// original frame rate dependent smoothing, so the same value does not produce the same result there.
12821294
/// </remarks>
12831295
public bool ScaleLerpSmoothing = true;
12841296
private bool m_PreviousScaleLerpSmoothing;
@@ -4259,14 +4271,13 @@ internal BufferedLinearInterpolatorQuaternion GetRotationInterpolator()
42594271
// Non-Authority
42604272
private void UpdateInterpolation()
42614273
{
4262-
// Use the local time because:
4263-
// Client-Server:
4264-
// Local time is server time on a host or server.
4265-
// Local time on clients takes latency into consideration.
4266-
// Distributed authority:
4267-
// Local time is used by the authority.
4268-
// Local time on non-authority takes latency into consid]eration.
4269-
var timeSystem = m_CachedNetworkManager.LocalTime;
4274+
// Use the server time, since that is the clock the states being interpolated between are stamped on
4275+
// (a state's SentTime is derived from its NetworkTick). Deriving the render time from LocalTime
4276+
// subtracts the tick latency from a clock that already leads ServerTime by roughly that much, which
4277+
// leaves the render time at or ahead of the newest state that can exist and starves the interpolator.
4278+
// Measuring from ServerTime is also self correcting, as the tick latency grows with the round trip
4279+
// time. This is a no-op on a host or server, where both clocks are the same.
4280+
var timeSystem = m_CachedNetworkManager.ServerTime;
42704281
var currentTime = timeSystem.Time;
42714282
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
42724283
var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime;
@@ -4730,7 +4741,10 @@ internal static float GetTickLatencyInSeconds(NetworkManager networkManager)
47304741
{
47314742
if (networkManager.IsListening)
47324743
{
4733-
return (float)networkManager.LocalTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset).Time;
4744+
// The number of ticks the interpolators run behind, as a duration. This is not a point in time:
4745+
// it does not grow as the session runs.
4746+
var ticksBehind = networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset;
4747+
return (float)(ticksBehind * networkManager.ServerTime.FixedDeltaTimeAsDouble);
47344748
}
47354749
return 0f;
47364750
}

com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,5 +301,103 @@ public void TestDuplicatedValues()
301301
Assert.That(interp, Is.EqualTo(2f));
302302
// Since there is no extrapolation, the rest of this test was removed.
303303
}
304+
305+
#region Lerp Smoothing
306+
307+
// Deliberately not round numbers, so exactly representable values cannot mask a defect.
308+
private const double k_SmoothTickInterval = 1.0d / 30.0d;
309+
private const int k_SmoothTickLatency = 2;
310+
private const float k_SmoothStartValue = 3.17f;
311+
private const float k_SmoothVelocity = 2.3f;
312+
private const double k_SmoothMoveDuration = 1.53d;
313+
private const double k_SmoothTotalDuration = 2.11d;
314+
315+
/// <summary>
316+
/// Drives the lerp and smooth dampening interpolation path with lerp smoothing enabled, where an
317+
/// authority moves at a constant velocity and then holds still while the non-authority renders at
318+
/// <paramref name="frameDeltaTime"/>.
319+
/// </summary>
320+
/// <returns>The interpolated value once <see cref="k_SmoothTotalDuration"/> has elapsed.</returns>
321+
private float RunLerpSmoothing(float maximumInterpolationTime, float frameDeltaTime, bool lerp)
322+
{
323+
var interpolator = new BufferedLinearInterpolatorFloat
324+
{
325+
MaximumInterpolationTime = maximumInterpolationTime,
326+
LerpSmoothEnabled = true,
327+
};
328+
interpolator.ResetTo(k_SmoothStartValue, 0.0d);
329+
330+
var restValue = k_SmoothStartValue + (float)(k_SmoothVelocity * k_SmoothMoveDuration);
331+
var maxDeltaTime = k_SmoothTickLatency * k_SmoothTickInterval;
332+
var nextTick = 1;
333+
var currentValue = k_SmoothStartValue;
334+
335+
for (var time = 0.0d; time < k_SmoothTotalDuration; time += frameDeltaTime)
336+
{
337+
// Deliver every state update whose send time has already passed.
338+
while (nextTick * k_SmoothTickInterval <= time)
339+
{
340+
var sentTime = nextTick * k_SmoothTickInterval;
341+
var sentValue = sentTime <= k_SmoothMoveDuration
342+
? k_SmoothStartValue + (float)(k_SmoothVelocity * sentTime)
343+
: restValue;
344+
interpolator.AddMeasurement(sentValue, sentTime);
345+
nextTick++;
346+
}
347+
348+
currentValue = interpolator.Update(frameDeltaTime, time - maxDeltaTime, k_SmoothTickInterval, maxDeltaTime, lerp);
349+
}
350+
351+
return currentValue;
352+
}
353+
354+
/// <summary>
355+
/// Lerp smoothing must still advance the value at 1.0f, the maximum legal value of the
356+
/// <see cref="Components.NetworkTransform.PositionMaxInterpolationTime"/> family of fields.
357+
/// </summary>
358+
[Test]
359+
public void LerpSmoothingDoesNotFreezeAtMaximumInterpolationTime([Values] bool lerp)
360+
{
361+
var result = RunLerpSmoothing(1.0f, 1.0f / 60.0f, lerp);
362+
363+
Assert.That(result, Is.GreaterThan(k_SmoothStartValue + 1.0f),
364+
$"Interpolated value only advanced {result - k_SmoothStartValue} from {k_SmoothStartValue} over " +
365+
$"{k_SmoothTotalDuration}s of authority motion. The maximum interpolation time froze the transform.");
366+
}
367+
368+
/// <summary>
369+
/// The rate at which lerp smoothing converges must not depend on the frame rate.
370+
/// </summary>
371+
[Test]
372+
public void LerpSmoothingIsFrameRateIndependent()
373+
{
374+
// Heavier than the default, where the frame rate dependency is measurable.
375+
const float maximumInterpolationTime = 0.87f;
376+
377+
var atThirtyFps = RunLerpSmoothing(maximumInterpolationTime, 1.0f / 30.0f, true);
378+
var atTwoFortyFps = RunLerpSmoothing(maximumInterpolationTime, 1.0f / 240.0f, true);
379+
380+
Assert.That(atThirtyFps, Is.EqualTo(atTwoFortyFps).Within(0.01f),
381+
$"The same elapsed time and interpolation settings produced {atThirtyFps} at 30fps but " +
382+
$"{atTwoFortyFps} at 240fps. The smoothing rate is scaling with the frame rate.");
383+
}
384+
385+
/// <summary>
386+
/// Only 1.0f is substituted for, so settings below it keep their own smoothing rate and a heavier
387+
/// setting stays smoother than a lighter one.
388+
/// </summary>
389+
[Test]
390+
public void LerpSmoothingPreservesSettingsBelowTheMaximum()
391+
{
392+
// 0.99f is the retention substituted for 1.0f, so clamping to it would collapse these two.
393+
var lighter = RunLerpSmoothing(0.99f, 1.0f / 60.0f, true);
394+
var heavier = RunLerpSmoothing(0.995f, 1.0f / 60.0f, true);
395+
396+
Assert.That(heavier, Is.LessThan(lighter),
397+
$"0.995 converged to {heavier} and 0.99 to {lighter} over the same motion. A higher maximum " +
398+
"interpolation time has to retain more of the previous value, so it cannot converge first.");
399+
}
400+
401+
#endregion
304402
}
305403
}

0 commit comments

Comments
 (0)