Skip to content

Commit b3722b1

Browse files
fix: new interpolators delaying between stopping and starting (#3413)
This fixes the issue where a server authority instance would lag behind when using the new interpolators. This was especially visible when the moving body completely stopped, all state updates were processed, and then shortly later it began to move again there would be a delay between the 1st state update and the rest of the pending state updates. This also fixes an issue where if the frame rate takes longer than the tick frequency authority instances could invoke the same check for state changes more than once in the same frame. This is not needed since changes to the transform are frame driven and the check should only be performed once per new tick(s). <!-- Add short version of the JIRA ticket to the PR title (e.g. "feat: new shiny feature [MTT-123]") --> ## Changelog - Fixed: Issue where the authority instance of `NetworkTransform` could check for state updates more than one time in a frame if the frame rate is slower than the tick frequency. - Fixed: Issue where the new interpolator types were blocking after the first consumption of a sequence of buffered state updates. ## Testing and Documentation - Includes the integration test: `InterpolationStopAndStartMotionTest`. - No documentation changes or additions were necessary. <!-- Uncomment and mark items off with a * if this PR deprecates any API: ### Deprecated API - [ ] An `[Obsolete]` attribute was added along with a `(RemovedAfter yyyy-mm-dd)` entry. - [ ] An [api updater] was added. - [ ] Deprecation of the API is explained in the CHANGELOG. - [ ] The users can understand why this API was removed and what they should use instead. --> ## Backport No backport is needed for this update since it pertains to 2.x only features.
1 parent 9a52e11 commit b3722b1

File tree

6 files changed

+295
-28
lines changed

6 files changed

+295
-28
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
1313

1414
### Fixed
1515

16+
- Fixed issue where the authority instance of NetworkTransform could check for state updates more than one time in a frame if the frame rate is greater than the tick frequency. (#3413)
17+
- Fixed issue where the new interpolator types were blocking after the first consumption of a sequence of buffered state updates. (#3413)
1618
- Fixed issue where root level in-scene placed `NetworkObject`s would only allow the ownership permission to be no less than distributable or sessionowner. (#3407)
1719

1820
### Changed

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

+5-11
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ public void Reset(T currentValue)
192192
TimeToTargetValue = 0.0f;
193193
DeltaTime = 0.0f;
194194
m_CurrentDeltaTime = 0.0f;
195+
MaxDeltaTime = 0.0f;
196+
LastRemainingTime = 0.0f;
195197
}
196198
}
197199

@@ -292,13 +294,9 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double
292294
var potentialItemNeedsProcessing = false;
293295

294296
// In the event there is nothing left in the queue (i.e. motion/change stopped), we still need to determine if the target has been reached.
295-
if (!noStateSet && m_BufferQueue.Count == 0)
297+
if (!noStateSet && !InterpolateState.TargetReached)
296298
{
297-
if (!InterpolateState.TargetReached)
298-
{
299-
InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision());
300-
}
301-
return;
299+
InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision());
302300
}
303301

304302
// Continue to process any remaining state updates in the queue (if any)
@@ -314,14 +312,10 @@ private void TryConsumeFromBuffer(double renderTime, double minDeltaTime, double
314312
if (!noStateSet)
315313
{
316314
potentialItemNeedsProcessing = ((potentialItem.TimeSent <= renderTime) && potentialItem.TimeSent > InterpolateState.Target.Value.TimeSent);
317-
if (!InterpolateState.TargetReached)
318-
{
319-
InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision());
320-
}
321315
}
322316

323317
// If we haven't set a target or we have another item that needs processing.
324-
if (noStateSet || potentialItemNeedsProcessing)
318+
if ((noStateSet && (potentialItem.TimeSent <= renderTime)) || potentialItemNeedsProcessing)
325319
{
326320
if (m_BufferQueue.TryDequeue(out BufferedItem target))
327321
{

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

+38-17
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,12 @@ public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReade
935935

936936
#region PROPERTIES AND GENERAL METHODS
937937

938+
/// <summary>
939+
/// Used on the authority side only.
940+
/// This is the current network tick and is set within <see cref="NetworkManager.NetworkUpdate(NetworkUpdateStage)"/>.
941+
/// </summary>
942+
internal static int CurrentTick;
943+
938944
/// <summary>
939945
/// Pertains to Owner Authority and Interpolation<br />
940946
/// When enabled (default), 1 additional tick is added to the total number of ticks used to calculate the tick latency ("ticks ago") as a time.
@@ -1878,6 +1884,8 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz
18781884
// If the state was explicitly set, then update the network tick to match the locally calculate tick
18791885
if (m_LocalAuthoritativeNetworkState.ExplicitSet)
18801886
{
1887+
// For explicit set, we use the current ServerTime.Tick and not CurrentTick since this is a SetState specific flow
1888+
// that is outside of the normal internal tick flow.
18811889
m_LocalAuthoritativeNetworkState.NetworkTick = m_CachedNetworkManager.NetworkTickSystem.ServerTime.Tick;
18821890
}
18831891

@@ -2011,7 +2019,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
20112019
// send a full frame synch.
20122020
var isAxisSync = false;
20132021
// We compare against the NetworkTickSystem version since ServerTime is set when updating ticks
2014-
if (UseUnreliableDeltas && !isSynchronization && m_DeltaSynch && m_NextTickSync <= m_CachedNetworkManager.NetworkTickSystem.ServerTime.Tick)
2022+
if (UseUnreliableDeltas && !isSynchronization && m_DeltaSynch && m_NextTickSync <= CurrentTick)
20152023
{
20162024
// Increment to the next frame synch tick position for this instance
20172025
m_NextTickSync += (int)m_CachedNetworkManager.NetworkConfig.TickRate;
@@ -2179,7 +2187,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
21792187
{
21802188
// If we are teleporting then we can skip the delta threshold check
21812189
isPositionDirty = networkState.IsTeleportingNextFrame || isAxisSync || forceState;
2182-
if (m_HalfFloatTargetTickOwnership > m_CachedNetworkManager.ServerTime.Tick)
2190+
if (m_HalfFloatTargetTickOwnership > CurrentTick)
21832191
{
21842192
isPositionDirty = true;
21852193
}
@@ -2225,7 +2233,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
22252233
networkState.NetworkDeltaPosition = m_HalfPositionState;
22262234

22272235
// If ownership offset is greater or we are doing an axial synchronization then synchronize the base position
2228-
if ((m_HalfFloatTargetTickOwnership > m_CachedNetworkManager.ServerTime.Tick || isAxisSync) && !networkState.IsTeleportingNextFrame)
2236+
if ((m_HalfFloatTargetTickOwnership > CurrentTick || isAxisSync) && !networkState.IsTeleportingNextFrame)
22292237
{
22302238
networkState.SynchronizeBaseHalfFloat = true;
22312239
}
@@ -2409,7 +2417,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra
24092417
if (enabled)
24102418
{
24112419
// We use the NetworkTickSystem version since ServerTime is set when updating ticks
2412-
networkState.NetworkTick = m_CachedNetworkManager.NetworkTickSystem.ServerTime.Tick;
2420+
networkState.NetworkTick = CurrentTick;
24132421
}
24142422
}
24152423

@@ -2440,7 +2448,7 @@ private void OnNetworkTick(bool isCalledFromParent = false)
24402448
}
24412449

24422450
// If we are nested and have already sent a state update this tick, then exit early (otherwise check for any changes in state)
2443-
if (IsNested && m_LocalAuthoritativeNetworkState.NetworkTick == m_CachedNetworkManager.ServerTime.Tick)
2451+
if (IsNested && m_LocalAuthoritativeNetworkState.NetworkTick == CurrentTick)
24442452
{
24452453
return;
24462454
}
@@ -4047,15 +4055,24 @@ public double GetPositionLastRemainingTime()
40474055
{
40484056
return m_PositionInterpolator.InterpolateState.LastRemainingTime;
40494057
}
4050-
#endif
4051-
4058+
#else
4059+
internal BufferedLinearInterpolatorVector3 GetPositionInterpolator()
4060+
{
4061+
return m_PositionInterpolator;
4062+
}
40524063

4064+
internal BufferedLinearInterpolatorQuaternion GetRotationInterpolator()
4065+
{
4066+
return m_RotationInterpolator;
4067+
}
4068+
#endif
40534069

40544070
// Non-Authority
40554071
private void UpdateInterpolation()
40564072
{
40574073
AdjustForChangeInTransformSpace();
4058-
var timeSystem = m_CachedNetworkManager.ServerTime;
4074+
// Select the time system relative to the type of NetworkManager instance.
4075+
var timeSystem = m_CachedNetworkManager.IsServer ? m_CachedNetworkManager.ServerTime : m_CachedNetworkManager.LocalTime;
40594076
var currentTime = timeSystem.Time;
40604077
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
40614078
var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime;
@@ -4501,6 +4518,15 @@ private void UpdateTransformState()
45014518

45024519
#region NETWORK TICK REGISTRATOIN AND HANDLING
45034520
private static Dictionary<NetworkManager, NetworkTransformTickRegistration> s_NetworkTickRegistration = new Dictionary<NetworkManager, NetworkTransformTickRegistration>();
4521+
4522+
internal static void UpdateNetworkTick(NetworkManager networkManager)
4523+
{
4524+
if (s_NetworkTickRegistration.ContainsKey(networkManager))
4525+
{
4526+
s_NetworkTickRegistration[networkManager].TickUpdate();
4527+
}
4528+
}
4529+
45044530
/// <summary>
45054531
/// Adjusts the over-all tick offset (i.e. how many ticks ago) and how wide of a maximum delta time will be used for the
45064532
/// various <see cref="InterpolationTypes"/>.
@@ -4562,9 +4588,8 @@ private static void RemoveTickUpdate(NetworkManager networkManager)
45624588
/// Having the tick update once and cycling through registered instances to update is evidently less processor
45634589
/// intensive than having each instance subscribe and update individually.
45644590
/// </summary>
4565-
private class NetworkTransformTickRegistration
4591+
internal class NetworkTransformTickRegistration
45664592
{
4567-
private Action m_NetworkTickUpdate;
45684593
private NetworkManager m_NetworkManager;
45694594
public HashSet<NetworkTransform> NetworkTransforms = new HashSet<NetworkTransform>();
45704595

@@ -4576,8 +4601,6 @@ private void OnNetworkManagerStopped(bool value)
45764601

45774602
public void Remove()
45784603
{
4579-
m_NetworkManager.NetworkTickSystem.Tick -= m_NetworkTickUpdate;
4580-
m_NetworkTickUpdate = null;
45814604
NetworkTransforms.Clear();
45824605
RemoveTickUpdate(m_NetworkManager);
45834606
}
@@ -4586,10 +4609,10 @@ public void Remove()
45864609
/// Invoked once per network tick, this will update any registered
45874610
/// authority instances.
45884611
/// </summary>
4589-
private void TickUpdate()
4612+
internal void TickUpdate()
45904613
{
45914614
// TODO FIX: The local NetworkTickSystem can invoke with the same network tick as before
4592-
if (m_NetworkManager.ServerTime.Tick <= m_LastTick)
4615+
if (CurrentTick <= m_LastTick)
45934616
{
45944617
return;
45954618
}
@@ -4600,13 +4623,11 @@ private void TickUpdate()
46004623
networkTransform.OnNetworkTick();
46014624
}
46024625
}
4603-
m_LastTick = m_NetworkManager.ServerTime.Tick;
4626+
m_LastTick = CurrentTick;
46044627
}
46054628
public NetworkTransformTickRegistration(NetworkManager networkManager)
46064629
{
46074630
m_NetworkManager = networkManager;
4608-
m_NetworkTickUpdate = new Action(TickUpdate);
4609-
networkManager.NetworkTickSystem.Tick += m_NetworkTickUpdate;
46104631
if (networkManager.IsServer)
46114632
{
46124633
networkManager.OnServerStopped += OnNetworkManagerStopped;

com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs

+13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#endif
1010
using UnityEngine.SceneManagement;
1111
using Debug = UnityEngine.Debug;
12+
using Unity.Netcode.Components;
1213

1314
namespace Unity.Netcode
1415
{
@@ -386,7 +387,19 @@ public void NetworkUpdate(NetworkUpdateStage updateStage)
386387
#endif
387388
case NetworkUpdateStage.PreUpdate:
388389
{
390+
var currentTick = ServerTime.Tick;
389391
NetworkTimeSystem.UpdateTime();
392+
if (ServerTime.Tick != currentTick)
393+
{
394+
// If we have a lower than expected frame rate and our number of ticks that have passed since the last
395+
// frame is greater than 1, then use the first next tick as opposed to the last tick when checking for
396+
// changes in transform state.
397+
// Note: This is an adjustment from using the NetworkTick event as that can be invoked more than once in
398+
// a single frame under the above condition and since any changes to the transform are frame driven there
399+
// is no need to check for changes to the transform more than once per frame.
400+
NetworkTransform.CurrentTick = (ServerTime.Tick - currentTick) > 1 ? currentTick + 1 : ServerTime.Tick;
401+
NetworkTransform.UpdateNetworkTick(this);
402+
}
390403
AnticipationSystem.Update();
391404
}
392405
break;

0 commit comments

Comments
 (0)