Skip to content

Commit a0e5d1f

Browse files
fix: Derive NetworkTransform interpolation time from the server clock
A NetworkTransform state's SentTime comes from its NetworkTick, which is a server tick, but the render time the interpolators were given was derived from LocalTime. That mixes two clocks. LocalTime leads ServerTime, so subtracting the tick latency from it lands the render time back at approximately ServerTime rather than a whole tick latency behind it, and a state's SentTime is floored to a tick boundary on top of that. The render time therefore sat at or ahead of the newest state that could exist and the interpolator had nothing to interpolate towards. Measuring from ServerTime makes the offset the whole tick latency instead of whatever is left of it, and is self correcting: as the round trip time grows the tick latency grows and the render time moves further back with it. This also matches the rest of the component, which already resets the interpolators using ServerTime. This is a no-op on a host or server, where the two clocks are the same, so it only affects clients. GetTickLatencyInSeconds returns an absolute time rather than a duration and had the same defect, so it now derives from ServerTime as well. GetTickLatency is left alone because it returns a tick count rather than a point in time.
1 parent b658b7e commit a0e5d1f

2 files changed

Lines changed: 24 additions & 9 deletions

File tree

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
2222

2323
### Fixed
2424

25+
- Issue where non-authority `NetworkTransform` instances derived their interpolation time from the local clock instead of the server clock that state updates are stamped on, which starved the interpolator and reduced interpolation to snapping between state updates. (#TBD)
26+
- Issue where `NetworkTransform.GetTickLatencyInSeconds` returned a time derived from the local clock, which did not match the time the interpolators actually use. (#TBD)
2527

2628
### Security
2729

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4230,14 +4230,27 @@ internal BufferedLinearInterpolatorQuaternion GetRotationInterpolator()
42304230
// Non-Authority
42314231
private void UpdateInterpolation()
42324232
{
4233-
// Use the local time because:
4234-
// Client-Server:
4235-
// Local time is server time on a host or server.
4236-
// Local time on clients takes latency into consideration.
4237-
// Distributed authority:
4238-
// Local time is used by the authority.
4239-
// Local time on non-authority takes latency into consid]eration.
4240-
var timeSystem = m_CachedNetworkManager.LocalTime;
4233+
// Use the server time, because that is the clock the measurements being interpolated between are
4234+
// stamped on: a state's SentTime is derived from its NetworkTick, which is a server tick.
4235+
//
4236+
// Deriving the render time from LocalTime instead mixes two clocks. LocalTime leads ServerTime by
4237+
// roughly the tick latency, so subtracting the tick latency from it lands the render time back at
4238+
// (approximately) ServerTime rather than behind it. "Approximately" is the problem: the lead is
4239+
// fractional while the subtraction is a whole number of ticks, and a state's SentTime is floored to
4240+
// a tick boundary on top of that. The render time therefore ends up at or slightly ahead of the
4241+
// newest state that can exist, leaving the interpolator with nothing to interpolate towards. A
4242+
// measured session had the render time ahead of ServerTime on 100% of frames, with the interpolator
4243+
// never holding more than one measurement.
4244+
//
4245+
// Measuring from ServerTime instead makes the offset the whole tick latency rather than whatever is
4246+
// left of it, which is self correcting: as the round trip time grows, NetworkTimeSystem.TickLatency
4247+
// grows and the render time moves further back with it.
4248+
//
4249+
// Note this is a no-op on a host or server, where LocalTime and ServerTime are the same.
4250+
// TODO-JIRA-TICKET:
4251+
// Confirm the distributed authority case. Authority instances interpolate nothing, so this should
4252+
// not reach them, but ServerTime's meaning under a CMB service session should be verified.
4253+
var timeSystem = m_CachedNetworkManager.ServerTime;
42414254
var currentTime = timeSystem.Time;
42424255
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
42434256
var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime;
@@ -4701,7 +4714,7 @@ internal static float GetTickLatencyInSeconds(NetworkManager networkManager)
47014714
{
47024715
if (networkManager.IsListening)
47034716
{
4704-
return (float)networkManager.LocalTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset).Time;
4717+
return (float)networkManager.ServerTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset).Time;
47054718
}
47064719
return 0f;
47074720
}

0 commit comments

Comments
 (0)