From 6eadea1bd229c00837d7915b6e831ff83ccee759 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 26 Feb 2025 17:03:07 -0600 Subject: [PATCH 01/41] update Some additional updates... still having a hard time getting a single lerp pass without some form of slight jitter. This includes a temporary 2nd pass lerp that still allows for the buffered item value to be completely reached. --- .../BufferedLinearInterpolator.cs | 216 ++++++++++++------ .../Runtime/Components/NetworkTransform.cs | 17 +- 2 files changed, 155 insertions(+), 78 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index d5fbaaa5cf..fbc3d1638f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using UnityEngine; namespace Unity.Netcode @@ -11,7 +12,7 @@ namespace Unity.Netcode /// The type of interpolated value public abstract class BufferedLinearInterpolator where T : struct { - internal float MaxInterpolationBound = 3.0f; + internal float MaxInterpolationBound = 1.0f; protected internal struct BufferedItem { public T Item; @@ -31,14 +32,7 @@ public BufferedItem(T item, double timeSent) private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator - protected internal T m_InterpStartValue; - protected internal T m_CurrentInterpValue; - protected internal T m_InterpEndValue; - - private double m_EndTimeConsumed; - private double m_StartTimeConsumed; - - protected internal readonly List m_Buffer = new List(k_BufferCountLimit); + protected internal readonly Queue m_Buffer = new Queue(k_BufferCountLimit); @@ -71,6 +65,7 @@ public BufferedItem(T item, double timeSent) private BufferedItem m_LastBufferedItemReceived; private int m_NbItemsReceivedThisFrame; + protected internal T m_CurrentInterpValue; private int m_LifetimeConsumedCount; private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0; @@ -96,8 +91,12 @@ internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) public void Clear() { m_Buffer.Clear(); - m_EndTimeConsumed = 0.0d; - m_StartTimeConsumed = 0.0d; + m_CurrentInterpValue = default; + InterpolateState = new CurrentState() + { + CurrentValue = default, + LerpT = 0.0000001f, + }; } /// @@ -108,18 +107,20 @@ public void Clear() public void ResetTo(T targetValue, double serverTime) { m_LifetimeConsumedCount = 1; - m_InterpStartValue = targetValue; - m_InterpEndValue = targetValue; - m_CurrentInterpValue = targetValue; m_Buffer.Clear(); - m_EndTimeConsumed = 0.0d; - m_StartTimeConsumed = 0.0d; - + m_Name = GetType().Name; + m_CurrentInterpValue = targetValue; + InterpolateState = new CurrentState() + { + CurrentValue = targetValue, + LerpT = 0.0000001f, + }; Update(0, serverTime, serverTime); } // 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 - private void TryConsumeFromBuffer(double renderTime, double serverTime) +#if OLDSTUFF + private void TryConsumeFromBufferLDK(double renderTime, double serverTime) { int consumedCount = 0; // only consume if we're ready @@ -152,15 +153,21 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) else if (consumedCount == 0) { // Interpolating to new value, end becomes start. We then look in our buffer for a new end. - m_StartTimeConsumed = m_EndTimeConsumed; - m_InterpStartValue = m_InterpEndValue; + + // !!!! This does not account for gaps between values !!! + // if last entry is > several ticks then the range is going to be very large! + m_StartTimeConsumed = renderTime; + m_InterpStartValue = m_CurrentInterpValue; } - if (bufferedValue.TimeSent > m_EndTimeConsumed) + if ((bufferedValue.TimeSent - m_StartTimeConsumed) >= k_TickFrequency) { itemToInterpolateTo = bufferedValue; m_EndTimeConsumed = bufferedValue.TimeSent; m_InterpEndValue = bufferedValue.Item; + m_Buffer.RemoveAt(i); + m_LifetimeConsumedCount++; + break; } } @@ -171,6 +178,80 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) } } } +#endif + private string m_Name; + + internal struct CurrentState + { + public BufferedItem? Start; + + public BufferedItem? End; + + public double RelativeTime; + + public T CurrentValue; + + public float LerpT; + } + + internal CurrentState InterpolateState; + + private void TryConsumeFromBuffer(double renderTime, double serverTime) + { + // If we don't have our initial buffered item/starting point or our end point or the end point's time sent is less than the + // render time + if (!InterpolateState.Start.HasValue || !InterpolateState.End.HasValue || InterpolateState.End.Value.TimeSent < renderTime) + { + BufferedItem? previousItem = null; + while (m_Buffer.TryPeek(out BufferedItem potentialItem)) + { + if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) + { + break; + } + + if (potentialItem.TimeSent <= serverTime) + { + // We want to initialize and then always set the end + if (!InterpolateState.Start.HasValue) + { + if (m_Buffer.TryDequeue(out BufferedItem start)) + { + InterpolateState.Start = start; + InterpolateState.RelativeTime = InterpolateState.Start.Value.TimeSent; + InterpolateState.CurrentValue = start.Item; + InterpolateState.LerpT = 0.0f; + InterpolateState.Start = start; + } + } + else if (!InterpolateState.End.HasValue || InterpolateState.End.Value.TimeSent < potentialItem.TimeSent) + { + if (m_Buffer.TryDequeue(out BufferedItem end)) + { + if (InterpolateState.End.HasValue) + { + InterpolateState.Start = InterpolateState.End; + //m_CurrentState.RelativeTime = m_CurrentState.End.Value.TimeSent; + } + InterpolateState.End = end; + InterpolateState.LerpT = 0.0f; + m_LifetimeConsumedCount++; + break; + } + } + else + { + break; + } + } + if (!InterpolateState.Start.HasValue) + { + break; + } + previousItem = potentialItem; + } + } + } /// /// Convenience version of 'Update' mainly for testing @@ -201,48 +282,33 @@ public T Update(float deltaTime, double renderTime, double serverTime) throw new InvalidOperationException("trying to update interpolator when no data has been added to it yet"); } - // Interpolation example to understand the math below - // 4 4.5 6 6.5 - // | | | | - // A render B Server - - if (m_LifetimeConsumedCount >= 1) // shouldn't interpolate between default values, let's wait to receive data first, should only interpolate between real measurements + // Only interpolate when there is a start and end point and we have not already reached the end value + if (InterpolateState.Start.HasValue && InterpolateState.End.HasValue) { - float t = 1.0f; - double range = m_EndTimeConsumed - m_StartTimeConsumed; - if (range > k_SmallValue) + if (InterpolateState.LerpT < 1.0f) { - var rangeFactor = 1.0f / (float)range; - - t = ((float)renderTime - (float)m_StartTimeConsumed) * rangeFactor; - - if (t < 0.0f) + InterpolateState.RelativeTime = Math.Clamp(InterpolateState.RelativeTime + deltaTime, 0.000001f, InterpolateState.End.Value.TimeSent); + //var t = 1.0f - Mathf.Clamp((float)((m_EndTimeConsumed - renderTime) * rangeFactor), 0.0f, 1.0f); + //var alt_t = 1.0f - Mathf.Clamp((float)((renderTime - m_StartTimeConsumed) * rangeFactor), 0.0f, 1.0f); + InterpolateState.LerpT = (float)(InterpolateState.RelativeTime / InterpolateState.End.Value.TimeSent); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.End.Value.Item, InterpolateState.LerpT); + if (InterpolateState.LerpT < 1.0f) { - // There is no mechanism to guarantee renderTime to not be before m_StartTimeConsumed - // This clamps t to a minimum of 0 and fixes issues with longer frames and pauses - - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogError($"renderTime was before m_StartTimeConsumed. This should never happen. {nameof(renderTime)} is {renderTime}, {nameof(m_StartTimeConsumed)} is {m_StartTimeConsumed}"); - } - t = 0.0f; + m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, InterpolateState.CurrentValue, 0.5f); } - - if (t > MaxInterpolationBound) // max extrapolation + else { - // TODO this causes issues with teleport, investigate - t = 1.0f; + m_CurrentInterpValue = InterpolateState.CurrentValue; } } - - var target = InterpolateUnclamped(m_InterpStartValue, m_InterpEndValue, t); - m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / MaximumInterpolationTime); // second interpolate to smooth out extrapolation jumps } m_NbItemsReceivedThisFrame = 0; return m_CurrentInterpValue; } + + private double m_LastSentTime = 0.0f; /// /// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value". /// @@ -261,7 +327,7 @@ public void AddMeasurement(T newMeasurement, double sentTime) m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); ResetTo(newMeasurement, sentTime); // Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues - m_Buffer.Add(m_LastBufferedItemReceived); + m_Buffer.Enqueue(m_LastBufferedItemReceived); } return; @@ -269,10 +335,15 @@ public void AddMeasurement(T newMeasurement, double sentTime) // Part the of reason for disabling extrapolation is how we add and use measurements over time. // TODO: Add detailed description of this area in Jira ticket - if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now + if (sentTime > m_LastSentTime || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now { m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); - m_Buffer.Add(m_LastBufferedItemReceived); + m_Buffer.Enqueue(m_LastBufferedItemReceived); + m_LastSentTime = sentTime; + } + else + { + Debug.Log($"[{m_Name}] Dropping measurement -- Time: {sentTime} Value: {newMeasurement}"); } } @@ -315,7 +386,7 @@ protected override float InterpolateUnclamped(float start, float end, float time { // Disabling Extrapolation: // TODO: Add Jira Ticket - return Mathf.Lerp(start, end, time); + return Mathf.LerpUnclamped(start, end, time); } /// @@ -348,11 +419,11 @@ protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion { if (IsSlerp) { - return Quaternion.Slerp(start, end, time); + return Quaternion.SlerpUnclamped(start, end, time); } else { - return Quaternion.Lerp(start, end, time); + return Quaternion.LerpUnclamped(start, end, time); } } @@ -384,16 +455,19 @@ private Quaternion ConvertToNewTransformSpace(Transform transform, Quaternion ro protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace) { - for (int i = 0; i < m_Buffer.Count; i++) + var buffer = m_Buffer.ToList(); + m_Buffer.Clear(); + for (int i = 0; i < buffer.Count; i++) { - var entry = m_Buffer[i]; + var entry = buffer[i]; entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace); - m_Buffer[i] = entry; + m_Buffer.Enqueue(entry); } - - m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace); + InterpolateState.CurrentValue = ConvertToNewTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); - m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace); + var end = InterpolateState.End.Value; + end.Item = ConvertToNewTransformSpace(transform, end.Item, inLocalSpace); + InterpolateState.End = end; base.OnConvertTransformSpace(transform, inLocalSpace); } @@ -414,11 +488,11 @@ protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, floa { if (IsSlerp) { - return Vector3.Slerp(start, end, time); + return Vector3.SlerpUnclamped(start, end, time); } else { - return Vector3.Lerp(start, end, time); + return Vector3.LerpUnclamped(start, end, time); } } @@ -450,16 +524,20 @@ private Vector3 ConvertToNewTransformSpace(Transform transform, Vector3 position protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace) { - for (int i = 0; i < m_Buffer.Count; i++) + var buffer = m_Buffer.ToList(); + m_Buffer.Clear(); + for (int i = 0; i < buffer.Count; i++) { - var entry = m_Buffer[i]; + var entry = buffer[i]; entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace); - m_Buffer[i] = entry; + m_Buffer.Enqueue(entry); } - m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace); + InterpolateState.CurrentValue = ConvertToNewTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); - m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace); + var end = InterpolateState.End.Value; + end.Item = ConvertToNewTransformSpace(transform, end.Item, inLocalSpace); + InterpolateState.End = end; base.OnConvertTransformSpace(transform, inLocalSpace); } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 7b2126b39d..3603f2f39f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1785,11 +1785,11 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var positionThreshold = Vector3.one * PositionThreshold; var rotationThreshold = Vector3.one * RotAngleThreshold; - if (m_UseRigidbodyForMotion) - { - positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold(); - rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold(); - } + //if (m_UseRigidbodyForMotion) + //{ + // positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold(); + // rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold(); + //} #else var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position; var rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; @@ -3704,10 +3704,9 @@ private void UpdateInterpolation() // is to make their cachedRenderTime run 2 ticks behind. // TODO: This could most likely just always be 2 - // var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1; - var ticksAgo = 2; - - var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time; + //var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1; + //var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time; + var cachedRenderTime = serverTime.TimeTicksAgo(2).Time; // Now only update the interpolators for the portions of the transform being synchronized if (SynchronizePosition) From 861dbf104e7bdcb4580c10aa4a6b12c11aaf9f97 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 27 Feb 2025 11:10:23 -0600 Subject: [PATCH 02/41] update Simplifying the new direction and getting all tests to pass. (still needs a smoother transition between buffered items when not finished interpolating from a previous one) --- .../BufferedLinearInterpolator.cs | 99 +++++++------------ 1 file changed, 36 insertions(+), 63 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index fbc3d1638f..62b597c2d1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -66,10 +66,7 @@ public BufferedItem(T item, double timeSent) private int m_NbItemsReceivedThisFrame; protected internal T m_CurrentInterpValue; - private int m_LifetimeConsumedCount; - - private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0; - + private double m_LastMeasurementAddedTime = 0.0f; internal bool EndOfBuffer => m_Buffer.Count == 0; internal bool InLocalSpace; @@ -92,11 +89,8 @@ public void Clear() { m_Buffer.Clear(); m_CurrentInterpValue = default; - InterpolateState = new CurrentState() - { - CurrentValue = default, - LerpT = 0.0000001f, - }; + m_LastMeasurementAddedTime = 0.0; + InterpolateState.Reset(default); } /// @@ -106,16 +100,13 @@ public void Clear() /// The current server time public void ResetTo(T targetValue, double serverTime) { - m_LifetimeConsumedCount = 1; - m_Buffer.Clear(); m_Name = GetType().Name; + // Clear everything first + Clear(); + // Set our initial value + InterpolateState.Reset(targetValue); + // TODO: If we get single lerping working, then m_CurrentInterpValue is no longer needed. m_CurrentInterpValue = targetValue; - InterpolateState = new CurrentState() - { - CurrentValue = targetValue, - LerpT = 0.0000001f, - }; - Update(0, serverTime, serverTime); } // 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 @@ -183,15 +174,21 @@ private void TryConsumeFromBufferLDK(double renderTime, double serverTime) internal struct CurrentState { - public BufferedItem? Start; - - public BufferedItem? End; + public BufferedItem? Target; public double RelativeTime; public T CurrentValue; public float LerpT; + + public void Reset(T currentValue) + { + CurrentValue = currentValue; + // When reset, we consider ourselves to have already arrived at the target (even if no target is set) + LerpT = 1.0f; + RelativeTime = 0.0; + } } internal CurrentState InterpolateState; @@ -200,42 +197,27 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) { // If we don't have our initial buffered item/starting point or our end point or the end point's time sent is less than the // render time - if (!InterpolateState.Start.HasValue || !InterpolateState.End.HasValue || InterpolateState.End.Value.TimeSent < renderTime) + if (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < renderTime) { BufferedItem? previousItem = null; while (m_Buffer.TryPeek(out BufferedItem potentialItem)) { + // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing + // to consume. if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) { break; } + // At a minimum, the next item should be equal to or less than the server time if (potentialItem.TimeSent <= serverTime) { - // We want to initialize and then always set the end - if (!InterpolateState.Start.HasValue) + if (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent) { - if (m_Buffer.TryDequeue(out BufferedItem start)) + if (m_Buffer.TryDequeue(out BufferedItem target)) { - InterpolateState.Start = start; - InterpolateState.RelativeTime = InterpolateState.Start.Value.TimeSent; - InterpolateState.CurrentValue = start.Item; + InterpolateState.Target = target; InterpolateState.LerpT = 0.0f; - InterpolateState.Start = start; - } - } - else if (!InterpolateState.End.HasValue || InterpolateState.End.Value.TimeSent < potentialItem.TimeSent) - { - if (m_Buffer.TryDequeue(out BufferedItem end)) - { - if (InterpolateState.End.HasValue) - { - InterpolateState.Start = InterpolateState.End; - //m_CurrentState.RelativeTime = m_CurrentState.End.Value.TimeSent; - } - InterpolateState.End = end; - InterpolateState.LerpT = 0.0f; - m_LifetimeConsumedCount++; break; } } @@ -244,7 +226,7 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) break; } } - if (!InterpolateState.Start.HasValue) + if (!InterpolateState.Target.HasValue) { break; } @@ -277,21 +259,16 @@ public T Update(float deltaTime, double renderTime, double serverTime) { TryConsumeFromBuffer(renderTime, serverTime); - if (InvalidState) - { - throw new InvalidOperationException("trying to update interpolator when no data has been added to it yet"); - } - // Only interpolate when there is a start and end point and we have not already reached the end value - if (InterpolateState.Start.HasValue && InterpolateState.End.HasValue) + if (InterpolateState.Target.HasValue) { if (InterpolateState.LerpT < 1.0f) { - InterpolateState.RelativeTime = Math.Clamp(InterpolateState.RelativeTime + deltaTime, 0.000001f, InterpolateState.End.Value.TimeSent); + InterpolateState.RelativeTime = Math.Clamp(InterpolateState.RelativeTime + deltaTime, 0.000001f, InterpolateState.Target.Value.TimeSent); //var t = 1.0f - Mathf.Clamp((float)((m_EndTimeConsumed - renderTime) * rangeFactor), 0.0f, 1.0f); //var alt_t = 1.0f - Mathf.Clamp((float)((renderTime - m_StartTimeConsumed) * rangeFactor), 0.0f, 1.0f); - InterpolateState.LerpT = (float)(InterpolateState.RelativeTime / InterpolateState.End.Value.TimeSent); - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.End.Value.Item, InterpolateState.LerpT); + InterpolateState.LerpT = (float)(InterpolateState.RelativeTime / InterpolateState.Target.Value.TimeSent); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); if (InterpolateState.LerpT < 1.0f) { m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, InterpolateState.CurrentValue, 0.5f); @@ -307,8 +284,6 @@ public T Update(float deltaTime, double renderTime, double serverTime) return m_CurrentInterpValue; } - - private double m_LastSentTime = 0.0f; /// /// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value". /// @@ -333,17 +308,15 @@ public void AddMeasurement(T newMeasurement, double sentTime) return; } - // Part the of reason for disabling extrapolation is how we add and use measurements over time. - // TODO: Add detailed description of this area in Jira ticket - if (sentTime > m_LastSentTime || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now + if (sentTime > m_LastMeasurementAddedTime) { m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); m_Buffer.Enqueue(m_LastBufferedItemReceived); - m_LastSentTime = sentTime; + m_LastMeasurementAddedTime = sentTime; } else { - Debug.Log($"[{m_Name}] Dropping measurement -- Time: {sentTime} Value: {newMeasurement}"); + Debug.Log($"[{m_Name}] Dropping measurement -- Time: {sentTime} Value: {newMeasurement} | Last measurement -- Time: {m_LastMeasurementAddedTime} Value: {m_LastBufferedItemReceived.Item}"); } } @@ -465,9 +438,9 @@ protected internal override void OnConvertTransformSpace(Transform transform, bo } InterpolateState.CurrentValue = ConvertToNewTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); - var end = InterpolateState.End.Value; + var end = InterpolateState.Target.Value; end.Item = ConvertToNewTransformSpace(transform, end.Item, inLocalSpace); - InterpolateState.End = end; + InterpolateState.Target = end; base.OnConvertTransformSpace(transform, inLocalSpace); } @@ -535,9 +508,9 @@ protected internal override void OnConvertTransformSpace(Transform transform, bo InterpolateState.CurrentValue = ConvertToNewTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); - var end = InterpolateState.End.Value; + var end = InterpolateState.Target.Value; end.Item = ConvertToNewTransformSpace(transform, end.Item, inLocalSpace); - InterpolateState.End = end; + InterpolateState.Target = end; base.OnConvertTransformSpace(transform, inLocalSpace); } From d5dfc636c90398fa139a3388733b09c8830a878e Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 27 Feb 2025 11:10:37 -0600 Subject: [PATCH 03/41] style adding some needed comments --- .../Runtime/Components/NetworkTransform.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 3603f2f39f..93184a982c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1808,9 +1808,15 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra if (InLocalSpace != networkState.InLocalSpace) #endif { + // When SwitchTransformSpaceWhenParented is set we automatically set our local space based on whether + // we are parented or not. networkState.InLocalSpace = SwitchTransformSpaceWhenParented ? transform.parent != null : InLocalSpace; isDirty = true; + // If SwitchTransformSpaceWhenParented is not set, then we will want to teleport networkState.IsTeleportingNextFrame = !SwitchTransformSpaceWhenParented; + // Otherwise, if SwitchTransformSpaceWhenParented is set we force a full state update. + // If interpolation is enabled, then any non-authority instance will update any pending + // buffered values to the correct world or local space values. forceState = SwitchTransformSpaceWhenParented; } #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D From 45ed0e11ea7002c627c1ad3ed34d7a396d4cb132 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 27 Feb 2025 16:42:02 -0600 Subject: [PATCH 04/41] update Still work in progress. Needs some clean up, but it is looking promising. --- .../BufferedLinearInterpolator.cs | 206 +++++++++++++++++- .../Runtime/Components/NetworkTransform.cs | 14 +- .../Tests/Editor/InterpolatorTests.cs | 86 ++++---- 3 files changed, 252 insertions(+), 54 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 62b597c2d1..251cffab6f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -15,13 +15,15 @@ public abstract class BufferedLinearInterpolator where T : struct internal float MaxInterpolationBound = 1.0f; protected internal struct BufferedItem { + public int ItemId; public T Item; public double TimeSent; - public BufferedItem(T item, double timeSent) + public BufferedItem(T item, double timeSent,int itemId) { Item = item; TimeSent = timeSent; + ItemId = itemId; } } @@ -34,7 +36,7 @@ public BufferedItem(T item, double timeSent) protected internal readonly Queue m_Buffer = new Queue(k_BufferCountLimit); - + private int m_BufferCount; // Buffer consumption scenarios // Perfect case consumption @@ -88,6 +90,7 @@ internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) public void Clear() { m_Buffer.Clear(); + m_BufferCount = 0; m_CurrentInterpValue = default; m_LastMeasurementAddedTime = 0.0; InterpolateState.Reset(default); @@ -176,18 +179,26 @@ internal struct CurrentState { public BufferedItem? Target; + public double ServerTime; public double RelativeTime; + public double DeltaTime; public T CurrentValue; + public T PreviousValue; public float LerpT; + public float LerpV; + public void Reset(T currentValue) { CurrentValue = currentValue; + PreviousValue = currentValue; // When reset, we consider ourselves to have already arrived at the target (even if no target is set) LerpT = 1.0f; + LerpV = 1.0f; RelativeTime = 0.0; + DeltaTime = 0.0; } } @@ -235,15 +246,192 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) } } + +#if UNITY_EDITOR + internal bool EnableLogging = false; + internal ulong NetworkObjectId; + internal ushort NetworkBehaviourId; + + private double m_LastDebugUpdate = 0.0; + + private double m_AvgTimeDelta = 0.0; + + private float m_LowestLerpT = float.MaxValue; + private float m_LerpTAverage = 0.0f; + private int m_MaxBufferedItems = 0; + private float m_AverageBufferCount = 0.0f; + + private void LogInfo(double serverTime) + { + if (!EnableLogging) + { + return; + } + if (m_LowestLerpT > InterpolateState.LerpT) + { + m_LowestLerpT = InterpolateState.LerpT; + } + + if (m_LerpTAverage == 0.0f) + { + m_LerpTAverage = InterpolateState.LerpT; + } + else + { + m_LerpTAverage = (m_LerpTAverage + InterpolateState.LerpT) * 0.5f; + } + + if (m_MaxBufferedItems < m_Buffer.Count) + { + m_MaxBufferedItems = m_Buffer.Count; + } + + if (m_AverageBufferCount == 0.0f) + { + m_AverageBufferCount = (m_AverageBufferCount + m_Buffer.Count) * 0.5f; + } + + if (m_AvgTimeDelta == 0.0) + { + m_AvgTimeDelta = InterpolateState.DeltaTime; + } + else + { + m_AvgTimeDelta += InterpolateState.DeltaTime; + m_AvgTimeDelta *= 0.5; + } + + if (m_LastDebugUpdate < serverTime) + { + Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}][{InterpolateState.Target.Value.ItemId}] Min LerpT: {m_LowestLerpT} | Avg LerpT: {m_LerpTAverage} | Max Count: {m_MaxBufferedItems} | Avg Count: {m_AverageBufferCount} | Avg TD: {m_AvgTimeDelta}"); + m_LastDebugUpdate = serverTime + 1.0; + m_AverageBufferCount = 0.0f; + m_MaxBufferedItems = 0; + m_LerpTAverage = 0.0f; + m_LowestLerpT = float.MaxValue; + } + } +#endif + + private void TryConsumeFromBuffer(NetworkTime networkTime, NetworkTimeSystem networkTimeSystem) + { + // If we don't have our initial buffered item/starting point or our end point or the end point's time sent is less than the + // render time + var renderTime = networkTime.TimeTicksAgo(2).Time; + if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime && InterpolateState.LerpT >= 1.0f)) + { + BufferedItem? previousItem = null; + while (m_Buffer.TryPeek(out BufferedItem potentialItem)) + { + // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing + // to consume. + if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) + { + break; + } + + // At a minimum, the next item should be equal to or less than the server time + if (potentialItem.TimeSent <= networkTimeSystem.ServerTime) + { + if (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent) + { + if (m_Buffer.TryDequeue(out BufferedItem target)) + { + if (!InterpolateState.Target.HasValue) + { + InterpolateState.Target = target; + InterpolateState.DeltaTime = networkTime.FixedDeltaTime; + } + else + { +#if UNITY_EDITOR + LogInfo(networkTimeSystem.ServerTime); +#endif + InterpolateState.DeltaTime = networkTime.FixedDeltaTime; + InterpolateState.Target = target; + } + //InterpolateState.RelativeTime = networkTime.FixedDeltaTime/2.75f; + //InterpolateState.RelativeTime = networkTime.FixedDeltaTime / 3f; + InterpolateState.RelativeTime = 0.0; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.LerpT = 0.0f; + InterpolateState.LerpV = 0.0f; + InterpolateState.ServerTime = networkTimeSystem.ServerTime; + break; + } + } + else + { + break; + } + } + if (!InterpolateState.Target.HasValue) + { + break; + } + previousItem = potentialItem; + } + } + } + + /// - /// Convenience version of 'Update' mainly for testing - /// the reason we don't want to always call this version is so that on the calling side we can compute - /// the renderTime once for the many things being interpolated (and the many interpolators per object) + /// ** Recommended Usage ** + /// A more precise update for the buffered linear interpolator. /// /// time since call /// current server time /// The newly interpolated value of type 'T' - public T Update(float deltaTime, NetworkTime serverTime) + public T Update(float deltaTime, NetworkTime serverTime, NetworkTimeSystem networkTimeSystem) + { + TryConsumeFromBuffer(serverTime, networkTimeSystem); + + + // Only interpolate when there is a start and end point and we have not already reached the end value + if (InterpolateState.Target.HasValue) + { + if (InterpolateState.LerpT < 1.0f) + { + var serverTimeDelta = Math.Max(0.000000000001, (networkTimeSystem.ServerTime - InterpolateState.ServerTime) + deltaTime); + + InterpolateState.RelativeTime = Math.Min(InterpolateState.RelativeTime + serverTimeDelta, InterpolateState.DeltaTime); + +#if UNITY_EDITOR + //if (EnableLogging) + //{ + // Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}][{InterpolateState.Target.Value.ItemId}] Server Time: {networkTimeSystem.ServerTime} | Time RDelta: {InterpolateState.RelativeTime} | Delta: {InterpolateState.DeltaTime}"); + //} +#endif + //var t = 1.0f - Mathf.Clamp((float)((m_EndTimeConsumed - renderTime) * rangeFactor), 0.0f, 1.0f); + //var alt_t = 1.0f - Mathf.Clamp((float)((renderTime - m_StartTimeConsumed) * rangeFactor), 0.0f, 1.0f); + InterpolateState.LerpT = (float)(InterpolateState.RelativeTime / InterpolateState.DeltaTime); + InterpolateState.CurrentValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); + m_CurrentInterpValue = InterpolateState.CurrentValue; + //m_CurrentInterpValue = Interpolate(InterpolateState.TargetStartValue, InterpolateState.TargetEndValue, (deltaTime / MaximumInterpolationTime)); + //if (InterpolateState.LerpT < 1.0f) + //{ + // m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, InterpolateState.CurrentValue, 0.5f); + //} + //else + //{ + // m_CurrentInterpValue = InterpolateState.CurrentValue; + //} + //if (InterpolateState.LerpT >= 1.0f) + //{ + // InterpolateState.TargetValue = InterpolateState.CurrentValue; + //} + //InterpolateState.LerpV = Math.Min(InterpolateState.LerpV + (deltaTime / MaximumInterpolationTime), 1.0f); + } + } + + m_NbItemsReceivedThisFrame = 0; + return m_CurrentInterpValue; + } + + /// + /// Used for internal testing + /// + internal T UpdateInternal(float deltaTime, NetworkTime serverTime) { return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); } @@ -299,7 +487,8 @@ public void AddMeasurement(T newMeasurement, double sentTime) { if (m_LastBufferedItemReceived.TimeSent < sentTime) { - m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); + m_BufferCount++; + m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); ResetTo(newMeasurement, sentTime); // Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues m_Buffer.Enqueue(m_LastBufferedItemReceived); @@ -310,7 +499,8 @@ public void AddMeasurement(T newMeasurement, double sentTime) if (sentTime > m_LastMeasurementAddedTime) { - m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); + m_BufferCount++; + m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); m_Buffer.Enqueue(m_LastBufferedItemReceived); m_LastMeasurementAddedTime = sentTime; } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 93184a982c..9dcf6b837d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3717,7 +3717,13 @@ private void UpdateInterpolation() // Now only update the interpolators for the portions of the transform being synchronized if (SynchronizePosition) { - m_PositionInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + //m_PositionInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); +#if UNITY_EDITOR + m_PositionInterpolator.NetworkObjectId = NetworkObjectId; + m_PositionInterpolator.NetworkBehaviourId = NetworkBehaviourId; + m_PositionInterpolator.EnableLogging = true; +#endif + m_PositionInterpolator.Update(cachedDeltaTime, m_CachedNetworkManager.ServerTime, m_CachedNetworkManager.NetworkTimeSystem); } if (SynchronizeRotation) @@ -3726,12 +3732,14 @@ private void UpdateInterpolation() // When using full precision Slerp towards the target rotation. /// m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; - m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + //m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + m_RotationInterpolator.Update(cachedDeltaTime, m_CachedNetworkManager.ServerTime, m_CachedNetworkManager.NetworkTimeSystem); } if (SynchronizeScale) { - m_ScaleInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + //m_ScaleInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + m_ScaleInterpolator.Update(cachedDeltaTime, m_CachedNetworkManager.ServerTime, m_CachedNetworkManager.NetworkTimeSystem); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs index b59f9093de..906f681e11 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs @@ -43,29 +43,29 @@ public void NormalUsage() // too small update, nothing happens, doesn't consume from buffer yet var serverTime = new NetworkTime(k_MockTickRate, 0.01d); // t = 0.1d - interpolator.Update(.01f, serverTime); + interpolator.UpdateInternal(.01f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0f)); // consume first measurement, still can't interpolate with just one tick consumed serverTime += 1.0d; // t = 1.01 - interpolator.Update(1.0f, serverTime); + interpolator.UpdateInternal(1.0f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0f)); // consume second measurement, start to interpolate serverTime += 1.0d; // t = 2.01 - var valueFromUpdate = interpolator.Update(1.0f, serverTime); + var valueFromUpdate = interpolator.UpdateInternal(1.0f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0.01f).Within(k_Precision)); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0.01f).Within(k_Precision)); // test a second time, to make sure the get doesn't update the value Assert.That(valueFromUpdate, Is.EqualTo(interpolator.GetInterpolatedValue()).Within(k_Precision)); // continue interpolation serverTime = new NetworkTime(k_MockTickRate, 2.5d); // t = 2.5d - interpolator.Update(2.5f - 2.01f, serverTime); + interpolator.UpdateInternal(2.5f - 2.01f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0.5f).Within(k_Precision)); // check when reaching end serverTime += 0.5d; // t = 3 - interpolator.Update(0.5f, serverTime); + interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1f).Within(k_Precision)); } @@ -86,22 +86,22 @@ public void OutOfOrderShouldStillWork() interpolator.AddMeasurement(2f, 2d); serverTime = new NetworkTime(k_MockTickRate, 1.5d); - interpolator.Update(1.5f, serverTime); + interpolator.UpdateInternal(1.5f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(0f).Within(k_Precision)); serverTime += timeStep; // t = 2.0 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1f).Within(k_Precision)); serverTime += timeStep; // t = 2.5 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1.5f).Within(k_Precision)); // makes sure that interpolation still continues in right direction interpolator.AddMeasurement(1, 1d); serverTime += timeStep; // t = 3 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2f).Within(k_Precision)); } @@ -123,64 +123,64 @@ public void MessageLoss() // first value teleports interpolator serverTime = new NetworkTime(k_MockTickRate, 1d); - interpolator.Update(1f, serverTime); + interpolator.UpdateInternal(1f, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1f)); // nothing happens, not ready to consume second value yet serverTime += timeStep; // t = 1.5 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1f)); // beginning of interpolation, second value consumed, currently at start serverTime += timeStep; // t = 2 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1f)); // interpolation starts serverTime += timeStep; // t = 2.5 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(1.5f)); serverTime += timeStep; // t = 3 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2f)); // extrapolating to 2.5 serverTime += timeStep; // t = 3.5d - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2.5f)); // next value skips to where it was supposed to be once buffer time is showing the next value serverTime += timeStep; // t = 4 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(3f)); // interpolation continues as expected serverTime += timeStep; // t = 4.5 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(3.5f)); serverTime += timeStep; // t = 5 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(4f)); // lost time=6, extrapolating serverTime += timeStep; // t = 5.5 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(4.5f)); serverTime += timeStep; // t = 6.0 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(5f)); // misprediction serverTime += timeStep; // t = 6.5 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(5.5f)); // lerp to right value serverTime += timeStep; // t = 7.0 - interpolator.Update((float)timeStep, serverTime); + interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.GreaterThan(6.0f)); Assert.That(interpolator.GetInterpolatedValue(), Is.LessThanOrEqualTo(100f)); } @@ -195,21 +195,21 @@ public void AddFirstMeasurement() interpolator.AddMeasurement(3f, 2d); serverTime += 1d; // t = 1 - var interpolatedValue = interpolator.Update(1f, serverTime); + var interpolatedValue = interpolator.UpdateInternal(1f, serverTime); // when consuming only one measurement and it's the first one consumed, teleport to it Assert.That(interpolatedValue, Is.EqualTo(2f)); // then interpolation should work as usual serverTime += 1d; // t = 2 - interpolatedValue = interpolator.Update(1f, serverTime); + interpolatedValue = interpolator.UpdateInternal(1f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(2f)); serverTime += 0.5d; // t = 2.5 - interpolatedValue = interpolator.Update(0.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(2.5f)); serverTime += 0.5d; // t = 3 - interpolatedValue = interpolator.Update(.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(3f)); } @@ -223,12 +223,12 @@ public void JumpToEachValueIfDeltaTimeTooBig() interpolator.AddMeasurement(3f, 2d); serverTime += 1d; // t = 1 - var interpolatedValue = interpolator.Update(1f, serverTime); + var interpolatedValue = interpolator.UpdateInternal(1f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(2f)); // big deltaTime, jumping to latest value serverTime += 9f; // t = 10 - interpolatedValue = interpolator.Update(8f, serverTime); + interpolatedValue = interpolator.UpdateInternal(8f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(3)); } @@ -248,7 +248,7 @@ public void JumpToLastValueFromStart() // big time jump serverTime += 7d; // t = 10 - var interpolatedValue = interpolator.Update(10f, serverTime); + var interpolatedValue = interpolator.UpdateInternal(10f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(3f)); // interpolation continues as normal @@ -256,19 +256,19 @@ public void JumpToLastValueFromStart() interpolator.AddMeasurement(11f, serverTime.Time); // out of order serverTime = new NetworkTime(k_MockTickRate, 10.5d); // t = 10.5 - interpolatedValue = interpolator.Update(0.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(3f)); serverTime += 0.5d; // t = 11 - interpolatedValue = interpolator.Update(0.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(10f)); serverTime += 0.5d; // t = 11.5 - interpolatedValue = interpolator.Update(0.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(10.5f)); serverTime += 0.5d; // t = 12 - interpolatedValue = interpolator.Update(0.5f, serverTime); + interpolatedValue = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(11f)); } @@ -281,7 +281,7 @@ public void TestBufferSizeLimit() var serverTime = new NetworkTime(k_MockTickRate, 0d); serverTime += 1.0d; // t = 1 interpolator.AddMeasurement(-1f, serverTime.Time); - interpolator.Update(1f, serverTime); + interpolator.UpdateInternal(1f, serverTime); // max + 1 serverTime += 1.0d; // t = 2 @@ -293,7 +293,7 @@ public void TestBufferSizeLimit() // client was paused for a while, some time has past, we just got a burst of values from the server that teleported us to the last value received serverTime = new NetworkTime(k_MockTickRate, 102d); - var interpolatedValue = interpolator.Update(101f, serverTime); + var interpolatedValue = interpolator.UpdateInternal(101f, serverTime); Assert.That(interpolatedValue, Is.EqualTo(102)); } @@ -303,7 +303,7 @@ public void TestUpdatingInterpolatorWithNoData() var interpolator = new BufferedLinearInterpolatorFloat(); var serverTime = new NetworkTime(k_MockTickRate, 0.0d); // invalid case, this is undefined behaviour - Assert.Throws(() => interpolator.Update(1f, serverTime)); + Assert.Throws(() => interpolator.UpdateInternal(1f, serverTime)); } [Ignore("TODO: Fix this test to still test duplicated values without extrapolation. This is tracked in MTT-11338")] @@ -323,38 +323,38 @@ public void TestDuplicatedValues() // empty interpolator teleports to initial value serverTime = new NetworkTime(k_MockTickRate, 0.0d); serverTime += 1d; // t = 1 - var interp = interpolator.Update(1f, serverTime); + var interp = interpolator.UpdateInternal(1f, serverTime); Assert.That(interp, Is.EqualTo(1f)); // consume value, start interp, currently at start value serverTime += 1d; // t = 2 - interp = interpolator.Update(1f, serverTime); + interp = interpolator.UpdateInternal(1f, serverTime); Assert.That(interp, Is.EqualTo(1f)); // interp serverTime += 0.5d; // t = 2.5 - interp = interpolator.Update(0.5f, serverTime); + interp = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interp, Is.EqualTo(1.5f)); // reach end serverTime += 0.5d; // t = 3 - interp = interpolator.Update(0.5f, serverTime); + interp = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interp, Is.EqualTo(2f)); // with unclamped interpolation, we continue mispredicting since the two last values are actually treated as the same. Therefore we're not stopping at "2" serverTime += 0.5d; // t = 3.5 - interp = interpolator.Update(0.5f, serverTime); + interp = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interp, Is.EqualTo(2.5f)); serverTime += 0.5d; // t = 4 - interp = interpolator.Update(0.5f, serverTime); + interp = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interp, Is.EqualTo(3f)); // we add a measurement with an updated time var pastServerTime = new NetworkTime(k_MockTickRate, 3.0d); interpolator.AddMeasurement(2f, pastServerTime.Time); - interp = interpolator.Update(0.5f, serverTime); + interp = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interp, Is.EqualTo(2f)); } } From 2e404947373b57430afef5c7d350300acd4d38c4 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 27 Feb 2025 16:42:28 -0600 Subject: [PATCH 05/41] update The base.OnUpdate is not needed here. --- .../Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs index 3398f041a9..d4585ff802 100644 --- a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/AutomatedPlayerMover.cs @@ -83,10 +83,6 @@ private void Update() } } } - else - { - base.OnUpdate(); - } } } } From b784e14c821ec8b88acf870ff2472464e74aaecf Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Sat, 1 Mar 2025 23:42:34 -0600 Subject: [PATCH 06/41] update WIP: Smooth dampening seems to be working better than any interpolation approach. Still have a minor timing issue, but I believe can be solved....much closer to a more resilient "interpolator". --- .../BufferedLinearInterpolator.cs | 381 ++++++++++++++---- .../Runtime/Components/NetworkTransform.cs | 31 +- .../Runtime/Core/NetworkManager.cs | 39 ++ .../Runtime/Timing/NetworkTimeSystem.cs | 5 + 4 files changed, 382 insertions(+), 74 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 251cffab6f..a9534cba93 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode @@ -18,12 +19,14 @@ protected internal struct BufferedItem public int ItemId; public T Item; public double TimeSent; + public float LerpT; - public BufferedItem(T item, double timeSent,int itemId) + public BufferedItem(T item, double timeSent, int itemId) { Item = item; TimeSent = timeSent; ItemId = itemId; + LerpT = 0.0f; } } @@ -94,6 +97,7 @@ public void Clear() m_CurrentInterpValue = default; m_LastMeasurementAddedTime = 0.0; InterpolateState.Reset(default); + TargetState.Reset(default); } /// @@ -179,30 +183,28 @@ internal struct CurrentState { public BufferedItem? Target; - public double ServerTime; + public double StartTime; public double RelativeTime; public double DeltaTime; + public float LerpT; + public T TargetValue; public T CurrentValue; public T PreviousValue; - - public float LerpT; - - public float LerpV; - public void Reset(T currentValue) { CurrentValue = currentValue; PreviousValue = currentValue; + TargetValue = currentValue; // When reset, we consider ourselves to have already arrived at the target (even if no target is set) LerpT = 1.0f; - LerpV = 1.0f; RelativeTime = 0.0; DeltaTime = 0.0; } } internal CurrentState InterpolateState; + internal CurrentState TargetState; private void TryConsumeFromBuffer(double renderTime, double serverTime) { @@ -303,7 +305,8 @@ private void LogInfo(double serverTime) if (m_LastDebugUpdate < serverTime) { - Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}][{InterpolateState.Target.Value.ItemId}] Min LerpT: {m_LowestLerpT} | Avg LerpT: {m_LerpTAverage} | Max Count: {m_MaxBufferedItems} | Avg Count: {m_AverageBufferCount} | Avg TD: {m_AvgTimeDelta}"); + //Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}][{InterpolateState.Target.Value.ItemId}] Min LerpT: {m_LowestLerpT} | Avg LerpT: {m_LerpTAverage} | Max Count: {m_MaxBufferedItems} | Avg Count: {m_AverageBufferCount} | Avg TD: {m_AvgTimeDelta}"); + Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}] Min LerpT: {m_LowestLerpT} | Avg LerpT: {m_LerpTAverage} | Max Count: {m_MaxBufferedItems} | Avg Count: {m_AverageBufferCount} | Avg TD: {m_AvgTimeDelta}"); m_LastDebugUpdate = serverTime + 1.0; m_AverageBufferCount = 0.0f; m_MaxBufferedItems = 0; @@ -311,16 +314,113 @@ private void LogInfo(double serverTime) m_LowestLerpT = float.MaxValue; } } -#endif + //private bool m_LogSegment; + + private float m_AvgDepth; + + private float m_AvgRTT; - private void TryConsumeFromBuffer(NetworkTime networkTime, NetworkTimeSystem networkTimeSystem) + private float m_MaxLerpT = 0.0f; + + private void LogSecondInfo(double serverTime, float lerpT, int depth, float serverHalfRtt) { - // If we don't have our initial buffered item/starting point or our end point or the end point's time sent is less than the - // render time - var renderTime = networkTime.TimeTicksAgo(2).Time; - if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime && InterpolateState.LerpT >= 1.0f)) + if (!EnableLogging) { + return; + } + if (m_LowestLerpT > lerpT) + { + m_LowestLerpT = lerpT; + } + + if (m_MaxLerpT < lerpT) + { + m_MaxLerpT = lerpT; + } + + if (m_LerpTAverage == 0.0f) + { + m_LerpTAverage = lerpT; + } + else + { + m_LerpTAverage = (m_LerpTAverage + lerpT) * 0.5f; + } + + if (m_AvgRTT == 0.0f) + { + m_AvgRTT = serverHalfRtt; + } + else + { + m_AvgRTT = (m_AvgRTT + serverHalfRtt) * 0.5f; + } + + if (m_AvgDepth == 0.0f) + { + m_AvgDepth = depth; + } + else + { + m_AvgDepth = (m_AvgDepth + depth) * 0.5f; + } + + + + if (m_MaxBufferedItems < m_Buffer.Count) + { + m_MaxBufferedItems = m_Buffer.Count; + } + + if (m_AverageBufferCount == 0.0f) + { + m_AverageBufferCount = (m_AverageBufferCount + m_Buffer.Count) * 0.5f; + } + + if (m_LastDebugUpdate < serverTime) + { + //Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}][{InterpolateState.Target.Value.ItemId}] Min LerpT: {m_LowestLerpT} | Avg LerpT: {m_LerpTAverage} | Max Count: {m_MaxBufferedItems} | Avg Count: {m_AverageBufferCount} | Avg TD: {m_AvgTimeDelta}"); + Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}] Min/Max LT: {m_LowestLerpT}/{m_MaxLerpT} | Avg LT: {m_LerpTAverage} | Max Count: {m_MaxBufferedItems} | Avg Count: {m_AverageBufferCount} | Avg Depth: {m_AvgDepth} Avg HRtt: {m_AvgRTT}"); + m_LastDebugUpdate = serverTime + 1.0; + m_AverageBufferCount = 0.0f; + m_MaxBufferedItems = 0; + m_LerpTAverage = 0.0f; + m_LowestLerpT = float.MaxValue; + m_AvgDepth = 0.0f; + m_AvgRTT = 0.0f; + m_MaxLerpT = 0.0f; + //m_LogSegment = true; + } + } +#endif + + /////////////////////////////// + /// Close.. + /// Non-Rigidbody: try changing to local time on clients and stick with server time on the server + /// Ridigbody: Not sure how to handle this one... time isn't updated until after FixedUpdate and + /// if we apply on fixed update but interpolate after fixed update then we will always be 1 full + /// frame behind... (hmmmm) + /////////////////////////////// + + /// + /// + /// + /// + /// + private void TryConsumeFromBuffer(double renderTime, NetworkTime serverTime, NetworkTimeSystem networkTimeSystem) + { + if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime))// && InterpolateState.LerpT >= 1.0f)) + { + //if (m_Buffer.Count == 0 && InterpolateState.Target.HasValue && (InterpolateState.Target.Value.TimeSent + (serverTime.FixedDeltaTime * 2) < serverTime.Time)) + //{ + // InterpolateState.Reset(InterpolateState.CurrentValue); + // InterpolateState.Target = null; + // m_SmoothVelocity1 = Vector3.zero; + // return; + //} BufferedItem? previousItem = null; + var startTime = 0.0; + var alreadyHasBufferItem = false; while (m_Buffer.TryPeek(out BufferedItem potentialItem)) { // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing @@ -331,33 +431,34 @@ private void TryConsumeFromBuffer(NetworkTime networkTime, NetworkTimeSystem net } // At a minimum, the next item should be equal to or less than the server time - if (potentialItem.TimeSent <= networkTimeSystem.ServerTime) + //if (potentialItem.TimeSent <= serverTime.Time) { - if (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent) + if (!InterpolateState.Target.HasValue || + (potentialItem.TimeSent <= renderTime && InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) { if (m_Buffer.TryDequeue(out BufferedItem target)) { if (!InterpolateState.Target.HasValue) { InterpolateState.Target = target; - InterpolateState.DeltaTime = networkTime.FixedDeltaTime; + InterpolateState.DeltaTime = serverTime.FixedDeltaTime; } else { -#if UNITY_EDITOR - LogInfo(networkTimeSystem.ServerTime); -#endif - InterpolateState.DeltaTime = networkTime.FixedDeltaTime; + if (!alreadyHasBufferItem) + { + alreadyHasBufferItem = true; + startTime = InterpolateState.Target.Value.TimeSent; + InterpolateState.StartTime = networkTimeSystem.RawTime; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.LerpT = 0.0f; + InterpolateState.RelativeTime = 0.0; + } + InterpolateState.DeltaTime = Math.Clamp(target.TimeSent - startTime, serverTime.FixedDeltaTime, serverTime.FixedDeltaTime * m_TimeLerp); InterpolateState.Target = target; + //InterpolateState.StartTime = serverTime.Time; } - //InterpolateState.RelativeTime = networkTime.FixedDeltaTime/2.75f; - //InterpolateState.RelativeTime = networkTime.FixedDeltaTime / 3f; - InterpolateState.RelativeTime = 0.0; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; - InterpolateState.LerpT = 0.0f; - InterpolateState.LerpV = 0.0f; - InterpolateState.ServerTime = networkTimeSystem.ServerTime; - break; + //break; } } else @@ -374,6 +475,89 @@ private void TryConsumeFromBuffer(NetworkTime networkTime, NetworkTimeSystem net } } + private T GetInterpolatedState(NetworkTimeSystem networkTimeSystem, NetworkTime renderTime, float fixedDeltaTime, int depth) + { + if (m_Buffer.Count < 2) + { + return m_Buffer.Count == 0 ? m_CurrentInterpValue : m_Buffer.ElementAt(0).Item; + } + //m_Buffer.ElementAt(0).LerpT = 0.0f; + //var timeFactor = (float)((networkTimeSystem.ServerTime - ((depth * fixedDeltaTime) + networkTimeSystem.LocalTimeOffset)) / (m_Buffer.ElementAt(1).TimeSent - m_Buffer.ElementAt(0).TimeSent)); + var deltaTime = m_Buffer.ElementAt(1).TimeSent - m_Buffer.ElementAt(0).TimeSent; + var minStart = fixedDeltaTime / 6; + var timeFactor = (float)((renderTime.Time - m_Buffer.ElementAt(0).TimeSent) / deltaTime); + float lerpT = Mathf.Clamp(timeFactor, minStart, 1.0f); +#if UNITY_EDITOR + LogSecondInfo(networkTimeSystem.LocalTime, lerpT, depth, (float)networkTimeSystem.LastSyncedRttSec); +#endif + return Interpolate(m_Buffer.ElementAt(0).Item, m_Buffer.ElementAt(1).Item, lerpT); + //if (m_Buffer.Count < depth) + //{ + // depth = m_Buffer.Count - 1; + //} + + //var previous = m_Buffer.ElementAt(0); + //var currentValue = previous.Item; + //var previousTime = previous.TimeSent; + ////float latencyFactor = fixedDeltaTime + (networkTimeSystem.LocalTimeOffset > fixedDeltaTime ? (float)networkTimeSystem.LocalTimeOffset : fixedDeltaTime); + + //var depthFactor = 1.0f / depth; + //for (int i = 1; i < depth; i++) + //{ + // var next = m_Buffer.ElementAt(i); + // var depthT = 1.0f - ((i - 1) * depthFactor); + // var adjustedTime = networkTimeSystem.ServerTime - previousTime; + // var depthAdjust = fixedDeltaTime * (depth - 1) * depthT; + // adjustedTime -= depthAdjust; + // if (adjustedTime < 0.0f) + // { + // continue; + // } + // var timeFactor = (float)(adjustedTime / (next.TimeSent - previousTime)); + // if (timeFactor < 0.0f) + // { + // Debug.Log($"TF: {timeFactor}!"); + // } + // float t = Mathf.Clamp(timeFactor, 0.0f, 1.0f); + // var previousValue = currentValue; + // currentValue = Interpolate(currentValue, next.Item, t); + // if (m_LogSegment) + // { + // Debug.Log($"[{i}] AdjT: {adjustedTime} DepthAdj: {depthAdjust} DepthT: {depthT} TF: {timeFactor} T: {t} Prev: {previousValue} Next: {next.Item} Current: {currentValue}"); + // } + // previousTime = next.TimeSent; + //} + //m_LogSegment = false; + //return currentValue; + } + + + private void ProcessStates(NetworkTimeSystem networkTimeSystem,NetworkTime renderTime, float fixedDeltaTime, int depth) + { + //float latencyFactor = (fixedDeltaTime * depth) + (networkTimeSystem.LocalTimeOffset > fixedDeltaTime ? (float)networkTimeSystem.LocalTimeOffset : fixedDeltaTime); + //var minimumTime = networkTimeSystem.ServerTime - ((depth * fixedDeltaTime) + networkTimeSystem.LocalTimeOffset); + var minimumTime = renderTime.Time; + while (m_Buffer.Count > 0 && m_Buffer.Peek().TimeSent < minimumTime) + { + m_Buffer.Dequeue(); + } + } + + private float m_TimeLerp = 4.0f; + + private Vector3 m_SmoothVelocity1; + private Vector3 m_SmoothVelocity2; + + protected virtual T SmoothDamp(T current, T target,ref Vector3 currentVelocity, float duration, float deltaTime, float maxSpeed = Mathf.Infinity) + { + return target; + } + + private const float k_DefaultPrecision = 0.0001f; + protected virtual bool IsAproximately(T first, T second, float precision = k_DefaultPrecision) + { + return false; + } /// /// ** Recommended Usage ** @@ -384,46 +568,48 @@ private void TryConsumeFromBuffer(NetworkTime networkTime, NetworkTimeSystem net /// The newly interpolated value of type 'T' public T Update(float deltaTime, NetworkTime serverTime, NetworkTimeSystem networkTimeSystem) { - TryConsumeFromBuffer(serverTime, networkTimeSystem); + //var timeLerp = Math.Clamp(2 * (float)(networkTimeSystem.LastSyncedRttSec / serverTime.FixedDeltaTime), 2, 8); + //////var renderTime = serverTime.TimeTicksAgo(depth, (float)serverTime.TickOffset); + //m_TimeLerp = Mathf.SmoothStep(m_TimeLerp, timeLerp, deltaTime); + var renderTime = serverTime.TimeTicksAgo((int)m_TimeLerp).Time; + //var renderTime = networkTimeSystem.RawTime - (serverTime.FixedDeltaTime * m_TimeLerp); + //var renderTime = serverTime.TimeTicksAgo(2).Time; +#if UNITY_EDITOR + //if (EnableLogging) + //{ + // Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}][{InterpolateState.Target.Value.ItemId}] Server Time: {networkTimeSystem.ServerTime} | Time RDelta: {InterpolateState.RelativeTime} | Delta: {InterpolateState.DeltaTime}"); + // Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}] RenderTime: {renderTime} Raw Time: {networkTimeSystem.RawTime}"); + //} +#endif + //var target = GetInterpolatedState(networkTimeSystem, renderTime, serverTime.FixedDeltaTime, depth); + ////m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, serverTime.FixedDeltaTime / MaximumInterpolationTime); + //m_CurrentInterpValue = target; + //ProcessStates(networkTimeSystem, renderTime, serverTime.FixedDeltaTime, depth); // Only interpolate when there is a start and end point and we have not already reached the end value - if (InterpolateState.Target.HasValue) - { - if (InterpolateState.LerpT < 1.0f) - { - var serverTimeDelta = Math.Max(0.000000000001, (networkTimeSystem.ServerTime - InterpolateState.ServerTime) + deltaTime); - - InterpolateState.RelativeTime = Math.Min(InterpolateState.RelativeTime + serverTimeDelta, InterpolateState.DeltaTime); - + if (InterpolateState.Target.HasValue && !IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item)) + { + InterpolateState.CurrentValue = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_SmoothVelocity1, (float)InterpolateState.DeltaTime, deltaTime); + //var timeDelta = Math.Max(0.000000000001, (serverTime.Time - InterpolateState.StartTime)); + //InterpolateState.RelativeTime = Math.Min(InterpolateState.RelativeTime + timeDelta, InterpolateState.DeltaTime); + //InterpolateState.LerpT = (float)(InterpolateState.RelativeTime / InterpolateState.DeltaTime); + + //InterpolateState.PreviousValue = InterpolateState.CurrentValue; + //InterpolateState.CurrentValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); + //m_CurrentInterpValue = InterpolateState.LerpT < 1.0f ? Interpolate(m_CurrentInterpValue, InterpolateState.CurrentValue, 0.333333333f) : InterpolateState.CurrentValue; + } + //else + //{ + // TryConsumeFromBuffer(renderTime, serverTime, networkTimeSystem); + //} + TryConsumeFromBuffer(renderTime, serverTime, networkTimeSystem); + m_CurrentInterpValue = InterpolateState.CurrentValue; + //m_CurrentInterpValue = SmoothDamp(m_CurrentInterpValue, InterpolateState.TargetValue, ref m_SmoothVelocity2, serverTime.FixedDeltaTime); + //TryConsumeFromBuffer(renderTime, serverTime, networkTimeSystem); #if UNITY_EDITOR - //if (EnableLogging) - //{ - // Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}][{InterpolateState.Target.Value.ItemId}] Server Time: {networkTimeSystem.ServerTime} | Time RDelta: {InterpolateState.RelativeTime} | Delta: {InterpolateState.DeltaTime}"); - //} + LogInfo(networkTimeSystem.ServerTime); #endif - //var t = 1.0f - Mathf.Clamp((float)((m_EndTimeConsumed - renderTime) * rangeFactor), 0.0f, 1.0f); - //var alt_t = 1.0f - Mathf.Clamp((float)((renderTime - m_StartTimeConsumed) * rangeFactor), 0.0f, 1.0f); - InterpolateState.LerpT = (float)(InterpolateState.RelativeTime / InterpolateState.DeltaTime); - InterpolateState.CurrentValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); - m_CurrentInterpValue = InterpolateState.CurrentValue; - //m_CurrentInterpValue = Interpolate(InterpolateState.TargetStartValue, InterpolateState.TargetEndValue, (deltaTime / MaximumInterpolationTime)); - //if (InterpolateState.LerpT < 1.0f) - //{ - // m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, InterpolateState.CurrentValue, 0.5f); - //} - //else - //{ - // m_CurrentInterpValue = InterpolateState.CurrentValue; - //} - //if (InterpolateState.LerpT >= 1.0f) - //{ - // InterpolateState.TargetValue = InterpolateState.CurrentValue; - //} - //InterpolateState.LerpV = Math.Min(InterpolateState.LerpV + (deltaTime / MaximumInterpolationTime), 1.0f); - } - } - m_NbItemsReceivedThisFrame = 0; return m_CurrentInterpValue; } @@ -453,8 +639,6 @@ public T Update(float deltaTime, double renderTime, double serverTime) if (InterpolateState.LerpT < 1.0f) { InterpolateState.RelativeTime = Math.Clamp(InterpolateState.RelativeTime + deltaTime, 0.000001f, InterpolateState.Target.Value.TimeSent); - //var t = 1.0f - Mathf.Clamp((float)((m_EndTimeConsumed - renderTime) * rangeFactor), 0.0f, 1.0f); - //var alt_t = 1.0f - Mathf.Clamp((float)((renderTime - m_StartTimeConsumed) * rangeFactor), 0.0f, 1.0f); InterpolateState.LerpT = (float)(InterpolateState.RelativeTime / InterpolateState.Target.Value.TimeSent); InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); if (InterpolateState.LerpT < 1.0f) @@ -504,10 +688,12 @@ public void AddMeasurement(T newMeasurement, double sentTime) m_Buffer.Enqueue(m_LastBufferedItemReceived); m_LastMeasurementAddedTime = sentTime; } - else +#if UNITY_EDITOR + else if (EnableLogging) { Debug.Log($"[{m_Name}] Dropping measurement -- Time: {sentTime} Value: {newMeasurement} | Last measurement -- Time: {m_LastMeasurementAddedTime} Value: {m_LastBufferedItemReceived.Item}"); } +#endif } /// @@ -552,6 +738,19 @@ protected override float InterpolateUnclamped(float start, float end, float time return Mathf.LerpUnclamped(start, end, time); } + protected override float SmoothDamp(float current, float target, ref Vector3 currentVelocity, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) + { + var x = currentVelocity.x; + var retSmooth = Mathf.SmoothDamp(current, target, ref x, duration, maxSpeed, deltaTime); + currentVelocity.x = x; + return retSmooth; + } + + protected override bool IsAproximately(float first, float second, float precision = 1E-07F) + { + return Mathf.Approximately(first, second); + } + /// protected override float Interpolate(float start, float end, float time) { @@ -603,6 +802,32 @@ protected override Quaternion Interpolate(Quaternion start, Quaternion end, floa } } + protected override Quaternion SmoothDamp(Quaternion current, Quaternion target, ref Vector3 currentVelocity, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) + { + Vector3 currentEuler = current.eulerAngles; + Vector3 targetEuler = target.eulerAngles; + currentEuler.x = Mathf.SmoothDampAngle(currentEuler.x, targetEuler.x, ref currentVelocity.x, duration, maxSpeed, deltaTime); + currentEuler.y = Mathf.SmoothDampAngle(currentEuler.y, targetEuler.y, ref currentVelocity.y, duration, maxSpeed, deltaTime); + currentEuler.z = Mathf.SmoothDampAngle(currentEuler.z, targetEuler.z, ref currentVelocity.z, duration, maxSpeed, deltaTime); + current.eulerAngles = currentEuler; + return current; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool Approximately(Quaternion a, Quaternion b, float precision) + { + return Mathf.Abs(a.x - b.x) <= precision && + Mathf.Abs(a.y - b.y) <= precision && + Mathf.Abs(a.z - b.z) <= precision && + Mathf.Abs(a.w - b.w) <= precision; + } + + protected override bool IsAproximately(Quaternion first, Quaternion second, float precision = 1E-07F) + { + return Approximately(first, second, precision); + //return Mathf.Abs(Quaternion.Dot(first, second)) >= 1 - precision; + } + private Quaternion ConvertToNewTransformSpace(Transform transform, Quaternion rotation, bool inLocalSpace) { if (inLocalSpace) @@ -672,6 +897,7 @@ protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time) } } + private Vector3 ConvertToNewTransformSpace(Transform transform, Vector3 position, bool inLocalSpace) { if (inLocalSpace) @@ -685,6 +911,25 @@ private Vector3 ConvertToNewTransformSpace(Transform transform, Vector3 position } } + protected override Vector3 SmoothDamp(Vector3 current, Vector3 target,ref Vector3 currentVelocity, float duration, float deltaTime, float maxSpeed) + { + return Vector3.SmoothDamp(current, target, ref currentVelocity, duration, maxSpeed, deltaTime); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool Approximately(Vector3 a, Vector3 b, float precision) + { + return Math.Round(Mathf.Abs(a.x - b.x), 2) <= precision && + Math.Round(Mathf.Abs(a.y - b.y), 2) <= precision && + Math.Round(Mathf.Abs(a.z - b.z), 2) <= precision; + } + + protected override bool IsAproximately(Vector3 first, Vector3 second, float precision = 1E-07F) + { + return Approximately(first, second, precision); + //return Vector3.Distance(first, second) < precision; + } + protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace) { var buffer = m_Buffer.ToList(); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 9dcf6b837d..08d7d5d62a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -115,6 +115,8 @@ internal uint BitSet // Used for NetworkDeltaPosition delta position synchronization internal int NetworkTick; + internal float TickOffset; + // Used when tracking by state ID is enabled internal int StateId; @@ -662,6 +664,8 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade // side updates on every new tick. BytePacker.WriteValueBitPacked(m_Writer, NetworkTick); + serializer.SerializeValue(ref TickOffset); + } else { @@ -669,6 +673,8 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade // We use network ticks as opposed to absolute time as the authoritative // side updates on every new tick. ByteUnpacker.ReadValueBitPacked(m_Reader, out NetworkTick); + + serializer.SerializeValue(ref TickOffset); } } @@ -2162,6 +2168,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra { // We use the NetworkTickSystem version since ServerTime is set when updating ticks networkState.NetworkTick = m_CachedNetworkManager.NetworkTickSystem.ServerTime.Tick; + networkState.TickOffset = (float)m_CachedNetworkManager.NetworkTickSystem.ServerTime.TickOffset; } } @@ -2880,7 +2887,7 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf } // Get the time when this new state was sent - newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkConfig.TickRate, newState.NetworkTick).Time; + newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkTickSystem.TickRate, newState.NetworkTick, newState.TickOffset).Time; if (LogStateUpdate) { @@ -3701,10 +3708,13 @@ private void UpdateInterpolation() var cachedServerTime = serverTime.Time; // var offset = (float)serverTime.TickOffset; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; + //var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; + var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; #else var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; #endif + + //var cachedDeltaTime = Time.deltaTime; // With owner authoritative mode, non-authority clients can lag behind // by more than 1 tick period of time. The current "solution" for now // is to make their cachedRenderTime run 2 ticks behind. @@ -3721,7 +3731,7 @@ private void UpdateInterpolation() #if UNITY_EDITOR m_PositionInterpolator.NetworkObjectId = NetworkObjectId; m_PositionInterpolator.NetworkBehaviourId = NetworkBehaviourId; - m_PositionInterpolator.EnableLogging = true; + m_PositionInterpolator.EnableLogging = false; #endif m_PositionInterpolator.Update(cachedDeltaTime, m_CachedNetworkManager.ServerTime, m_CachedNetworkManager.NetworkTimeSystem); } @@ -3770,6 +3780,18 @@ public virtual void OnUpdate() #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D + internal void OnInterpolateFixedUpdate() + { + // If not spawned or this instance has authority, exit early + if (!m_UseRigidbodyForMotion || !IsSpawned || CanCommitToTransform) + { + return; + } + + // Update interpolation + UpdateInterpolation(); + } + /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . @@ -3784,9 +3806,6 @@ public virtual void OnFixedUpdate() m_NetworkRigidbodyInternal.WakeIfSleeping(); - // Update interpolation - UpdateInterpolation(); - // Apply the current authoritative state ApplyAuthoritativeState(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 9f257fb5e1..afef5c4b0f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -333,6 +333,25 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) MessageManager.CleanupDisconnectedClients(); AnticipationSystem.ProcessReanticipation(); + + //foreach (var networkObjectEntry in NetworkTransformFixedUpdate) + //{ + // // if not active or not spawned then skip + // if (!networkObjectEntry.Value.gameObject.activeInHierarchy || !networkObjectEntry.Value.IsSpawned) + // { + // continue; + // } + + // foreach (var networkTransformEntry in networkObjectEntry.Value.NetworkTransforms) + // { + // // only update if enabled + // if (networkTransformEntry.enabled) + // { + // networkTransformEntry.OnInterpolateFixedUpdate(); + // networkTransformEntry.OnFixedUpdate(); + // } + // } + //} } break; #if COM_UNITY_MODULES_PHYSICS @@ -351,6 +370,7 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) // only update if enabled if (networkTransformEntry.enabled) { + networkTransformEntry.OnInterpolateFixedUpdate(); networkTransformEntry.OnFixedUpdate(); } } @@ -362,6 +382,25 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) { NetworkTimeSystem.UpdateTime(); AnticipationSystem.Update(); + + //foreach (var networkObjectEntry in NetworkTransformFixedUpdate) + //{ + // // if not active or not spawned then skip + // if (!networkObjectEntry.Value.gameObject.activeInHierarchy || !networkObjectEntry.Value.IsSpawned) + // { + // continue; + // } + + // foreach (var networkTransformEntry in networkObjectEntry.Value.NetworkTransforms) + // { + // // only update if enabled + // if (networkTransformEntry.enabled) + // { + // networkTransformEntry.OnInterpolateFixedUpdate(); + // //networkTransformEntry.OnFixedUpdate(); + // } + // } + //} } break; case NetworkUpdateStage.PreLateUpdate: diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index a40c88ed0a..a9c95c8750 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -252,5 +252,10 @@ public void Sync(double serverTimeSec, double rttSec) // the TimeSyncMessage should only take half of the RTT time (legacy was using 1 full RTT) m_DesiredLocalTimeOffset = timeDif + (rttSec * 0.5d) + LocalBufferSec; } + + internal double ServerTimeOffset => m_DesiredServerTimeOffset; + internal double LocalTimeOffset => m_DesiredLocalTimeOffset; + + internal double RawTime => m_TimeSec; } } From 08f261c3d321b4ec2e38ea132fddb165d3b14618 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 4 Mar 2025 15:00:20 -0600 Subject: [PATCH 07/41] update Time systems related updates --- .../Runtime/Timing/NetworkTime.cs | 14 ++++++++-- .../Runtime/Timing/NetworkTimeSystem.cs | 28 ++++++++++++++++--- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs index 916a35e6c4..083956aa84 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs @@ -45,11 +45,19 @@ public struct NetworkTime public double FixedTime => m_CachedTick * m_TickInterval; /// - /// Gets the fixed delta time. This value is based on the and stays constant. - /// Similar to There is no equivalent to . + /// Gets the fixed delta time. This value is calculated by dividing 1.0 by the and stays constant. /// + /// + /// This could result in a potential floating point precision variance on different systems.
+ /// See for a more precise value. + ///
public float FixedDeltaTime => (float)m_TickInterval; + /// + /// Gets the fixed delta time as a double. This value is calculated by dividing 1.0 by the and stays constant. + /// + public double FixedDeltaTimeAsDouble => m_TickInterval; + /// /// Gets the amount of network ticks which have passed until reaching the current time value. /// @@ -70,7 +78,7 @@ public NetworkTime(uint tickRate) Assert.IsTrue(tickRate > 0, "Tickrate must be a positive value."); m_TickRate = tickRate; - m_TickInterval = 1f / m_TickRate; // potential floating point precision issue, could result in different interval on different machines + m_TickInterval = 1.0 / m_TickRate; m_CachedTickOffset = 0; m_CachedTick = 0; m_TimeSec = 0; diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index a9c95c8750..1a26d555f6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -1,5 +1,6 @@ using System; using Unity.Profiling; +using UnityEngine; namespace Unity.Netcode { @@ -35,7 +36,7 @@ public class NetworkTimeSystem #if DEVELOPMENT_BUILD || UNITY_EDITOR private static ProfilerMarker s_SyncTime = new ProfilerMarker($"{nameof(NetworkManager)}.SyncTime"); #endif - + private double m_PreviousTimeSec; private double m_TimeSec; private double m_CurrentLocalTimeOffset; private double m_DesiredLocalTimeOffset; @@ -75,8 +76,20 @@ public class NetworkTimeSystem ///
public double ServerTime => m_TimeSec + m_CurrentServerTimeOffset; + private float m_TickLatencyAverage = 2.0f; + + /// + /// The averaged latency in network ticks between a client and server. + /// + /// + /// For a distributed authority network topology, this latency is between + /// the client and the distributed authority service instance. + /// + public int TickLatency = 2; + internal double LastSyncedServerTimeSec { get; private set; } internal double LastSyncedRttSec { get; private set; } + internal double LastSyncedHalfRttSec { get; private set; } private NetworkConnectionManager m_ConnectionManager; private NetworkTransport m_NetworkTransport; @@ -101,6 +114,7 @@ public NetworkTimeSystem(double localBufferSec, double serverBufferSec = k_Defau ServerBufferSec = serverBufferSec; HardResetThresholdSec = hardResetThresholdSec; AdjustmentRatio = adjustmentRatio; + m_TickLatencyAverage = 2; } /// @@ -203,7 +217,14 @@ public static NetworkTimeSystem ServerTimeSystem() /// True if a hard reset of the time system occurred due to large time offset differences. False if normal time advancement occurred public bool Advance(double deltaTimeSec) { + m_PreviousTimeSec = m_TimeSec; m_TimeSec += deltaTimeSec; + // TODO: For client-server, we need a latency message sent by clients to tell us their tick latency + if (LastSyncedRttSec > 0.0f) + { + m_TickLatencyAverage = Mathf.Lerp(m_TickLatencyAverage, (float)((LastSyncedRttSec + deltaTimeSec) / m_NetworkTickSystem.ServerTime.FixedDeltaTimeAsDouble), (float)deltaTimeSec); + TickLatency = (int)Mathf.Max(1.0f, Mathf.Round(m_TickLatencyAverage)) + 1; + } if (Math.Abs(m_DesiredLocalTimeOffset - m_CurrentLocalTimeOffset) > HardResetThresholdSec || Math.Abs(m_DesiredServerTimeOffset - m_CurrentServerTimeOffset) > HardResetThresholdSec) { @@ -243,6 +264,7 @@ public void Reset(double serverTimeSec, double rttSec) public void Sync(double serverTimeSec, double rttSec) { LastSyncedRttSec = rttSec; + LastSyncedHalfRttSec = (rttSec * 0.5d); LastSyncedServerTimeSec = serverTimeSec; var timeDif = serverTimeSec - m_TimeSec; @@ -250,12 +272,10 @@ public void Sync(double serverTimeSec, double rttSec) m_DesiredServerTimeOffset = timeDif - ServerBufferSec; // We adjust our desired local time offset to be half RTT since the delivery of // the TimeSyncMessage should only take half of the RTT time (legacy was using 1 full RTT) - m_DesiredLocalTimeOffset = timeDif + (rttSec * 0.5d) + LocalBufferSec; + m_DesiredLocalTimeOffset = timeDif + LastSyncedHalfRttSec + LocalBufferSec; } internal double ServerTimeOffset => m_DesiredServerTimeOffset; internal double LocalTimeOffset => m_DesiredLocalTimeOffset; - - internal double RawTime => m_TimeSec; } } From 4ce6c50c53703d5b5e0cb86acf449732021c7806 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 4 Mar 2025 15:01:01 -0600 Subject: [PATCH 08/41] update NetworkTransform and BufferedLinearInterpolator related updates. --- .../Editor/NetworkTransformEditor.cs | 6 + .../BufferedLinearInterpolator.cs | 998 ++++++------------ .../BufferedLinearInterpolatorFloat.cs | 42 + .../BufferedLinearInterpolatorFloat.cs.meta | 2 + .../BufferedLinearInterpolatorQuaternion.cs | 83 ++ ...fferedLinearInterpolatorQuaternion.cs.meta | 2 + .../BufferedLinearInterpolatorVector3.cs | 77 ++ .../BufferedLinearInterpolatorVector3.cs.meta | 2 + .../Runtime/Components/NetworkTransform.cs | 244 +++-- 9 files changed, 651 insertions(+), 805 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs.meta diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index e93226e2bd..3c1182e3e3 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -28,6 +28,7 @@ public class NetworkTransformEditor : NetcodeEditorBase private SerializedProperty m_ScaleThresholdProperty; private SerializedProperty m_InLocalSpaceProperty; private SerializedProperty m_InterpolateProperty; + private SerializedProperty m_InterpolationTypeProperty; private SerializedProperty m_UseQuaternionSynchronization; private SerializedProperty m_UseQuaternionCompression; @@ -61,6 +62,7 @@ public override void OnEnable() m_ScaleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleThreshold)); m_InLocalSpaceProperty = serializedObject.FindProperty(nameof(NetworkTransform.InLocalSpace)); m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate)); + m_InterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.InterpolationType)); m_UseQuaternionSynchronization = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionSynchronization)); m_UseQuaternionCompression = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionCompression)); m_UseHalfFloatPrecision = serializedObject.FindProperty(nameof(NetworkTransform.UseHalfFloatPrecision)); @@ -159,6 +161,10 @@ private void DisplayNetworkTransformProperties() if (!networkTransform.HideInterpolateValue) { EditorGUILayout.PropertyField(m_InterpolateProperty); + if (networkTransform.Interpolate) + { + EditorGUILayout.PropertyField(m_InterpolationTypeProperty); + } } EditorGUILayout.PropertyField(m_SlerpPosition); EditorGUILayout.PropertyField(m_UseQuaternionSynchronization); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index a9534cba93..e72b350899 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode @@ -13,33 +11,81 @@ namespace Unity.Netcode /// The type of interpolated value public abstract class BufferedLinearInterpolator where T : struct { - internal float MaxInterpolationBound = 1.0f; + private const float k_AproximatePrecision = 0.0001f; + protected internal struct BufferedItem { public int ItemId; public T Item; public double TimeSent; - public float LerpT; public BufferedItem(T item, double timeSent, int itemId) { Item = item; TimeSent = timeSent; ItemId = itemId; - LerpT = 0.0f; } } + internal struct CurrentState + { + public BufferedItem? Target; - /// - /// There's two factors affecting interpolation: buffering (set in NetworkManager's NetworkTimeSystem) and interpolation time, which is the amount of time it'll take to reach the target. This is to affect the second one. - /// - public float MaximumInterpolationTime = 0.1f; + public double StartTime; + public double RelativeTime; + public float TimeToTargetValue; + public float DeltaTime; + public float LerpT; - private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator + public T TargetValue; + public T CurrentValue; + public T PreviousValue; - protected internal readonly Queue m_Buffer = new Queue(k_BufferCountLimit); + private float m_AverageDeltaTime; - private int m_BufferCount; + public float AverageDeltaTime => m_AverageDeltaTime; + public float FinalTimeToTarget => TimeToTargetValue - DeltaTime; + + public bool AddDeltaTime(float deltaTime) + { + if (m_AverageDeltaTime == 0.0f) + { + m_AverageDeltaTime = deltaTime; + } + else + { + // Gradually adjust our delta time to keep this + // value more consistent + m_AverageDeltaTime = 3.0f * m_AverageDeltaTime; + m_AverageDeltaTime += deltaTime; + m_AverageDeltaTime *= 0.25f; + } + DeltaTime = Math.Max(DeltaTime + m_AverageDeltaTime, TimeToTargetValue); + LerpT = DeltaTime / TimeToTargetValue; + return FinalTimeToTarget <= m_AverageDeltaTime; + } + + public bool TargetTimeAproximatelyReached() + { + if (!Target.HasValue) + { + return false; + } + return (m_AverageDeltaTime * 0.3333333f) >= FinalTimeToTarget; + } + + public void Reset(T currentValue) + { + Target = null; + CurrentValue = currentValue; + PreviousValue = currentValue; + TargetValue = currentValue; + // When reset, we consider ourselves to have already arrived at the target (even if no target is set) + LerpT = 1.0f; + RelativeTime = 0.0; + DeltaTime = 0.0f; + m_AverageDeltaTime = 0.0f; + } + } // Buffer consumption scenarios // Perfect case consumption @@ -67,6 +113,14 @@ public BufferedItem(T item, double timeSent, int itemId) // Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so // that we don't have a very small buffer because of this. private const int k_BufferCountLimit = 100; + + /// + /// There's two factors affecting interpolation: buffering (set in NetworkManager's NetworkTimeSystem) and interpolation time, which is the amount of time it'll take to reach the target. This is to affect the second one. + /// + public float MaximumInterpolationTime = 0.1f; + + private int m_BufferCount; + private BufferedItem m_LastBufferedItemReceived; private int m_NbItemsReceivedThisFrame; @@ -76,19 +130,41 @@ public BufferedItem(T item, double timeSent, int itemId) internal bool InLocalSpace; - protected internal virtual void OnConvertTransformSpace(Transform transform, bool inLocalSpace) - { + /// + /// The current interpolation state + /// + internal CurrentState InterpolateState; + protected internal readonly Queue m_Buffer = new Queue(k_BufferCountLimit); - } + /// + /// Represents the rate of change for the value being interpolated when smooth dampening is enabled. + /// + private T m_RateOfChange; + + private bool m_UseSmoothDamening; + protected bool UseSmoothDampening => m_UseSmoothDamening; + + private bool m_IsAngularValue; + protected bool IsAngularValue => m_IsAngularValue; internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) { - OnConvertTransformSpace(transform, inLocalSpace); + var count = m_Buffer.Count; + for (int i = 0; i < count; i++) + { + var entry = m_Buffer.Dequeue(); + entry.Item = OnConvertTransformSpace(transform, entry.Item, inLocalSpace); + m_Buffer.Enqueue(entry); + } + InterpolateState.CurrentValue = OnConvertTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); + var end = InterpolateState.Target.Value; + end.Item = OnConvertTransformSpace(transform, end.Item, inLocalSpace); + InterpolateState.Target = end; InLocalSpace = inLocalSpace; } /// - /// Resets interpolator to initial state + /// Resets interpolator to the defaults. /// public void Clear() { @@ -97,159 +173,278 @@ public void Clear() m_CurrentInterpValue = default; m_LastMeasurementAddedTime = 0.0; InterpolateState.Reset(default); - TargetState.Reset(default); + m_RateOfChange = default; } /// - /// Teleports current interpolation value to targetValue. + /// Resets the current interpolator to the target valueTeleports current interpolation value to targetValue. + /// /// - /// The target value to teleport instantly + /// + /// This is used when first synchronizing/initializing and when telporting an object. + /// + /// The target value to reset the interpolator to /// The current server time - public void ResetTo(T targetValue, double serverTime) + /// Defaults to true and is the recommened way to achieve a smoother interpolation between buffer item values. + /// When rotation is expressed as Euler values (i.e. Vector3 and/or float) this helps determine what kind of smooth dampening to use. + public void ResetTo(T targetValue, double serverTime, bool useSmoothDampening = true, bool isAngularValue = false) { +#if UNITY_EDITOR m_Name = GetType().Name; +#endif // Clear everything first Clear(); // Set our initial value InterpolateState.Reset(targetValue); // TODO: If we get single lerping working, then m_CurrentInterpValue is no longer needed. m_CurrentInterpValue = targetValue; + + m_UseSmoothDamening = useSmoothDampening; + m_IsAngularValue = isAngularValue; + + // Add the first measurement for our baseline + AddMeasurement(targetValue, serverTime); } - // 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 -#if OLDSTUFF - private void TryConsumeFromBufferLDK(double renderTime, double serverTime) + /// + /// TryConsumeFromBuffer: Smooth Dampening Version + /// + /// render time: the time in "ticks ago" relative to the current tick latency + /// minimum time delta (defaults to tick frequency) + /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer + private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime) { - int consumedCount = 0; - // only consume if we're ready - - // this operation was measured as one of our most expensive, and we should put some thought into this. - // NetworkTransform has (currently) 7 buffered linear interpolators (3 position, 3 scale, 1 rot), and - // each has its own independent buffer and 'm_endTimeConsume'. That means every frame I have to do 7x - // these checks vs. if we tracked these values in a unified way - if (renderTime >= m_EndTimeConsumed) + if (!InterpolateState.Target.HasValue || + (InterpolateState.Target.Value.TimeSent <= renderTime && + (InterpolateState.TargetTimeAproximatelyReached() || + IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item)))) { - BufferedItem? itemToInterpolateTo = null; - // assumes we're using sequenced messages for netvar syncing - // buffer contains oldest values first, iterating from end to start to remove elements from list while iterating - - // calling m_Buffer.Count shows up hot in the profiler. - 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 + BufferedItem? previousItem = null; + var startTime = 0.0; + var alreadyHasBufferItem = false; + while (m_Buffer.TryPeek(out BufferedItem potentialItem)) { - var bufferedValue = m_Buffer[i]; - // Consume when ready and interpolate to last value we can consume. This can consume multiple values from the buffer - if (bufferedValue.TimeSent <= serverTime) + // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing + // to consume. + if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) { - if (!itemToInterpolateTo.HasValue || bufferedValue.TimeSent > itemToInterpolateTo.Value.TimeSent) + break; + } + + // If we haven't set a target or the potential item's time sent is less that the current target's time sent + // then pull the BufferedItem from the queue. The second portion of this accounts for scenarios where there + // was bad latency and the buffer has more than one item in the queue that is less than the renderTime. Under + // this scenario, we just want to continue pulling items from the queue until the last item pulled from the + // queue is greater than the redner time or greater than the currently targeted item. + if (!InterpolateState.Target.HasValue || + (potentialItem.TimeSent <= renderTime && InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) + { + if (m_Buffer.TryDequeue(out BufferedItem target)) { - if (m_LifetimeConsumedCount == 0) + if (!InterpolateState.Target.HasValue) { - // if interpolator not initialized, teleport to first value when available - m_StartTimeConsumed = bufferedValue.TimeSent; - m_InterpStartValue = bufferedValue.Item; - } - else if (consumedCount == 0) - { - // Interpolating to new value, end becomes start. We then look in our buffer for a new end. + InterpolateState.Target = target; - // !!!! This does not account for gaps between values !!! - // if last entry is > several ticks then the range is going to be very large! - m_StartTimeConsumed = renderTime; - m_InterpStartValue = m_CurrentInterpValue; + alreadyHasBufferItem = true; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + startTime = InterpolateState.Target.Value.TimeSent; } - - if ((bufferedValue.TimeSent - m_StartTimeConsumed) >= k_TickFrequency) + else { - itemToInterpolateTo = bufferedValue; - m_EndTimeConsumed = bufferedValue.TimeSent; - m_InterpEndValue = bufferedValue.Item; - m_Buffer.RemoveAt(i); - m_LifetimeConsumedCount++; - break; + if (!alreadyHasBufferItem) + { + alreadyHasBufferItem = true; + startTime = InterpolateState.Target.Value.TimeSent; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.LerpT = 0.0f; + } + // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated + // for each item as opposed to losing the resolution of the values. + InterpolateState.TimeToTargetValue = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); + InterpolateState.Target = target; } + InterpolateState.DeltaTime = 0.0f; } + } + else + { + break; + } - m_Buffer.RemoveAt(i); - consumedCount++; - m_LifetimeConsumedCount++; + if (!InterpolateState.Target.HasValue) + { + break; } + previousItem = potentialItem; } } } -#endif - private string m_Name; - internal struct CurrentState + /// + /// Used for internal testing + /// + internal T UpdateInternal(float deltaTime, NetworkTime serverTime) { - public BufferedItem? Target; - - public double StartTime; - public double RelativeTime; - public double DeltaTime; - public float LerpT; + return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); + } - public T TargetValue; - public T CurrentValue; - public T PreviousValue; - public void Reset(T currentValue) + /// + /// ** Recommended to use when is enabled. **
+ /// Provides a closer/more precise update between the current and target values that uses a smooth dampening approach. + ///
+ /// The last frame time that is either for non-rigidbody motion and when using ridigbody motion. + /// The tick latency in relative local time. + /// The minimum time delta between the current and target value. + /// The maximum time delta between the current and target value. + /// The newly interpolated value of type 'T' + public T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, float maxDeltaTime) + { + TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime); + // Only interpolate when there is a start and end point and we have not already reached the end value + if (InterpolateState.Target.HasValue) { - CurrentValue = currentValue; - PreviousValue = currentValue; - TargetValue = currentValue; - // When reset, we consider ourselves to have already arrived at the target (even if no target is set) - LerpT = 1.0f; - RelativeTime = 0.0; - DeltaTime = 0.0; + InterpolateState.AddDeltaTime(deltaTime); + InterpolateState.CurrentValue = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime, 10000.0f); } + m_NbItemsReceivedThisFrame = 0; + return InterpolateState.CurrentValue; } - internal CurrentState InterpolateState; - internal CurrentState TargetState; + /// + /// Call to update the state of the interpolators using Lerp. + /// + /// + /// This approah uses double lerping which can result in an over-smoothed result. + /// + /// time since last call + /// our current time + /// current server time + /// The newly interpolated value of type 'T' + public T Update(float deltaTime, double renderTime, double serverTime) + { + TryConsumeFromBuffer(renderTime, deltaTime, (float)(serverTime - renderTime)); + // Only interpolate when there is a start and end point and we have not already reached the end value + if (InterpolateState.Target.HasValue) + { + InterpolateState.AddDeltaTime(deltaTime); + var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, deltaTime / MaximumInterpolationTime); + } + m_NbItemsReceivedThisFrame = 0; + return InterpolateState.CurrentValue; + } - private void TryConsumeFromBuffer(double renderTime, double serverTime) + /// + /// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value". + /// + /// The new measurement value to use + /// The time to record for measurement + public void AddMeasurement(T newMeasurement, double sentTime) { - // If we don't have our initial buffered item/starting point or our end point or the end point's time sent is less than the - // render time - if (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < renderTime) + m_NbItemsReceivedThisFrame++; + + // 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 + // 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 + if (m_NbItemsReceivedThisFrame > k_BufferCountLimit) { - BufferedItem? previousItem = null; - while (m_Buffer.TryPeek(out BufferedItem potentialItem)) + if (m_LastBufferedItemReceived.TimeSent < sentTime) { - // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing - // to consume. - if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) - { - break; - } - - // At a minimum, the next item should be equal to or less than the server time - if (potentialItem.TimeSent <= serverTime) - { - if (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent) - { - if (m_Buffer.TryDequeue(out BufferedItem target)) - { - InterpolateState.Target = target; - InterpolateState.LerpT = 0.0f; - break; - } - } - else - { - break; - } - } - if (!InterpolateState.Target.HasValue) - { - break; - } - previousItem = potentialItem; + m_BufferCount++; + m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); + ResetTo(newMeasurement, sentTime); + // Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues + m_Buffer.Enqueue(m_LastBufferedItemReceived); } + return; + } + + // Drop measurements that are received out of order/late + if (sentTime > m_LastMeasurementAddedTime) + { + m_BufferCount++; + m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); + m_Buffer.Enqueue(m_LastBufferedItemReceived); + m_LastMeasurementAddedTime = sentTime; + } +#if UNITY_EDITOR + else if (EnableLogging) + { + Debug.Log($"[{m_Name}] Dropping measurement -- Time: {sentTime} Value: {newMeasurement} | Last measurement -- Time: {m_LastMeasurementAddedTime} Value: {m_LastBufferedItemReceived.Item}"); } +#endif + } + + /// + /// Gets latest value from the interpolator. This is updated every update as time goes by. + /// + /// The current interpolated value of type 'T' + public T GetInterpolatedValue() + { + return InterpolateState.CurrentValue; + } + + /// + /// Method to override and adapted to the generic type. This assumes interpolation for that value will be clamped. + /// + /// The start value (min) + /// The end value (max) + /// The time value used to interpolate between start and end values (pos) + /// The interpolated value + protected abstract T Interpolate(T start, T end, float time); + + /// + /// Method to override and adapted to the generic type. This assumes interpolation for that value will not be clamped. + /// + /// The start value (min) + /// The end value (max) + /// The time value used to interpolate between start and end values (pos) + /// The interpolated value + protected abstract T InterpolateUnclamped(T start, T end, float time); + + + /// + /// An alternate smoothing method to Lerp. + /// + /// Current item value. + /// Target item value. + /// The velocity of change. + /// Total time to smooth between the and . + /// The increasing delta time from when start to finish. + /// Maximum rate of change per pass. + /// The smoothed value. + protected internal virtual T SmoothDamp(T current, T target, ref T rateOfChange, float duration, float deltaTime, float maxSpeed = Mathf.Infinity) + { + return target; + } + + /// + /// Determines if two values of type are close to the same value. + /// + /// First value of type . + /// Second value of type . + /// The precision of the aproximation. + /// + protected internal virtual bool IsAproximately(T first, T second, float precision = k_AproximatePrecision) + { + return false; } + /// + /// Converts a value of type from world to local space or vice versa. + /// + /// Reference transform + /// local or world space (true or false) + protected internal virtual T OnConvertTransformSpace(Transform transform, T item, bool inLocalSpace) + { + return default; + } + // TODO: Collect data points so a single buffered linear interpolator can provide additional data points + // to be visualized in RNSM. + #region DEBUG_LOGGING #if UNITY_EDITOR + + private string m_Name; internal bool EnableLogging = false; internal ulong NetworkObjectId; internal ushort NetworkBehaviourId; @@ -365,7 +560,7 @@ private void LogSecondInfo(double serverTime, float lerpT, int depth, float serv m_AvgDepth = (m_AvgDepth + depth) * 0.5f; } - + if (m_MaxBufferedItems < m_Buffer.Count) { @@ -393,561 +588,6 @@ private void LogSecondInfo(double serverTime, float lerpT, int depth, float serv } } #endif - - /////////////////////////////// - /// Close.. - /// Non-Rigidbody: try changing to local time on clients and stick with server time on the server - /// Ridigbody: Not sure how to handle this one... time isn't updated until after FixedUpdate and - /// if we apply on fixed update but interpolate after fixed update then we will always be 1 full - /// frame behind... (hmmmm) - /////////////////////////////// - - /// - /// - /// - /// - /// - private void TryConsumeFromBuffer(double renderTime, NetworkTime serverTime, NetworkTimeSystem networkTimeSystem) - { - if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime))// && InterpolateState.LerpT >= 1.0f)) - { - //if (m_Buffer.Count == 0 && InterpolateState.Target.HasValue && (InterpolateState.Target.Value.TimeSent + (serverTime.FixedDeltaTime * 2) < serverTime.Time)) - //{ - // InterpolateState.Reset(InterpolateState.CurrentValue); - // InterpolateState.Target = null; - // m_SmoothVelocity1 = Vector3.zero; - // return; - //} - BufferedItem? previousItem = null; - var startTime = 0.0; - var alreadyHasBufferItem = false; - while (m_Buffer.TryPeek(out BufferedItem potentialItem)) - { - // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing - // to consume. - if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) - { - break; - } - - // At a minimum, the next item should be equal to or less than the server time - //if (potentialItem.TimeSent <= serverTime.Time) - { - if (!InterpolateState.Target.HasValue || - (potentialItem.TimeSent <= renderTime && InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) - { - if (m_Buffer.TryDequeue(out BufferedItem target)) - { - if (!InterpolateState.Target.HasValue) - { - InterpolateState.Target = target; - InterpolateState.DeltaTime = serverTime.FixedDeltaTime; - } - else - { - if (!alreadyHasBufferItem) - { - alreadyHasBufferItem = true; - startTime = InterpolateState.Target.Value.TimeSent; - InterpolateState.StartTime = networkTimeSystem.RawTime; - InterpolateState.PreviousValue = InterpolateState.CurrentValue; - InterpolateState.LerpT = 0.0f; - InterpolateState.RelativeTime = 0.0; - } - InterpolateState.DeltaTime = Math.Clamp(target.TimeSent - startTime, serverTime.FixedDeltaTime, serverTime.FixedDeltaTime * m_TimeLerp); - InterpolateState.Target = target; - //InterpolateState.StartTime = serverTime.Time; - } - //break; - } - } - else - { - break; - } - } - if (!InterpolateState.Target.HasValue) - { - break; - } - previousItem = potentialItem; - } - } - } - - private T GetInterpolatedState(NetworkTimeSystem networkTimeSystem, NetworkTime renderTime, float fixedDeltaTime, int depth) - { - if (m_Buffer.Count < 2) - { - return m_Buffer.Count == 0 ? m_CurrentInterpValue : m_Buffer.ElementAt(0).Item; - } - //m_Buffer.ElementAt(0).LerpT = 0.0f; - //var timeFactor = (float)((networkTimeSystem.ServerTime - ((depth * fixedDeltaTime) + networkTimeSystem.LocalTimeOffset)) / (m_Buffer.ElementAt(1).TimeSent - m_Buffer.ElementAt(0).TimeSent)); - var deltaTime = m_Buffer.ElementAt(1).TimeSent - m_Buffer.ElementAt(0).TimeSent; - var minStart = fixedDeltaTime / 6; - var timeFactor = (float)((renderTime.Time - m_Buffer.ElementAt(0).TimeSent) / deltaTime); - float lerpT = Mathf.Clamp(timeFactor, minStart, 1.0f); -#if UNITY_EDITOR - LogSecondInfo(networkTimeSystem.LocalTime, lerpT, depth, (float)networkTimeSystem.LastSyncedRttSec); -#endif - return Interpolate(m_Buffer.ElementAt(0).Item, m_Buffer.ElementAt(1).Item, lerpT); - //if (m_Buffer.Count < depth) - //{ - // depth = m_Buffer.Count - 1; - //} - - //var previous = m_Buffer.ElementAt(0); - //var currentValue = previous.Item; - //var previousTime = previous.TimeSent; - ////float latencyFactor = fixedDeltaTime + (networkTimeSystem.LocalTimeOffset > fixedDeltaTime ? (float)networkTimeSystem.LocalTimeOffset : fixedDeltaTime); - - //var depthFactor = 1.0f / depth; - //for (int i = 1; i < depth; i++) - //{ - // var next = m_Buffer.ElementAt(i); - // var depthT = 1.0f - ((i - 1) * depthFactor); - // var adjustedTime = networkTimeSystem.ServerTime - previousTime; - // var depthAdjust = fixedDeltaTime * (depth - 1) * depthT; - // adjustedTime -= depthAdjust; - // if (adjustedTime < 0.0f) - // { - // continue; - // } - // var timeFactor = (float)(adjustedTime / (next.TimeSent - previousTime)); - // if (timeFactor < 0.0f) - // { - // Debug.Log($"TF: {timeFactor}!"); - // } - // float t = Mathf.Clamp(timeFactor, 0.0f, 1.0f); - // var previousValue = currentValue; - // currentValue = Interpolate(currentValue, next.Item, t); - // if (m_LogSegment) - // { - // Debug.Log($"[{i}] AdjT: {adjustedTime} DepthAdj: {depthAdjust} DepthT: {depthT} TF: {timeFactor} T: {t} Prev: {previousValue} Next: {next.Item} Current: {currentValue}"); - // } - // previousTime = next.TimeSent; - //} - //m_LogSegment = false; - //return currentValue; - } - - - private void ProcessStates(NetworkTimeSystem networkTimeSystem,NetworkTime renderTime, float fixedDeltaTime, int depth) - { - //float latencyFactor = (fixedDeltaTime * depth) + (networkTimeSystem.LocalTimeOffset > fixedDeltaTime ? (float)networkTimeSystem.LocalTimeOffset : fixedDeltaTime); - //var minimumTime = networkTimeSystem.ServerTime - ((depth * fixedDeltaTime) + networkTimeSystem.LocalTimeOffset); - var minimumTime = renderTime.Time; - while (m_Buffer.Count > 0 && m_Buffer.Peek().TimeSent < minimumTime) - { - m_Buffer.Dequeue(); - } - } - - private float m_TimeLerp = 4.0f; - - private Vector3 m_SmoothVelocity1; - private Vector3 m_SmoothVelocity2; - - protected virtual T SmoothDamp(T current, T target,ref Vector3 currentVelocity, float duration, float deltaTime, float maxSpeed = Mathf.Infinity) - { - return target; - } - - private const float k_DefaultPrecision = 0.0001f; - protected virtual bool IsAproximately(T first, T second, float precision = k_DefaultPrecision) - { - return false; - } - - /// - /// ** Recommended Usage ** - /// A more precise update for the buffered linear interpolator. - /// - /// time since call - /// current server time - /// The newly interpolated value of type 'T' - public T Update(float deltaTime, NetworkTime serverTime, NetworkTimeSystem networkTimeSystem) - { - //var timeLerp = Math.Clamp(2 * (float)(networkTimeSystem.LastSyncedRttSec / serverTime.FixedDeltaTime), 2, 8); - //////var renderTime = serverTime.TimeTicksAgo(depth, (float)serverTime.TickOffset); - //m_TimeLerp = Mathf.SmoothStep(m_TimeLerp, timeLerp, deltaTime); - var renderTime = serverTime.TimeTicksAgo((int)m_TimeLerp).Time; - //var renderTime = networkTimeSystem.RawTime - (serverTime.FixedDeltaTime * m_TimeLerp); - //var renderTime = serverTime.TimeTicksAgo(2).Time; -#if UNITY_EDITOR - //if (EnableLogging) - //{ - // Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}][{InterpolateState.Target.Value.ItemId}] Server Time: {networkTimeSystem.ServerTime} | Time RDelta: {InterpolateState.RelativeTime} | Delta: {InterpolateState.DeltaTime}"); - // Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}] RenderTime: {renderTime} Raw Time: {networkTimeSystem.RawTime}"); - //} -#endif - //var target = GetInterpolatedState(networkTimeSystem, renderTime, serverTime.FixedDeltaTime, depth); - ////m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, serverTime.FixedDeltaTime / MaximumInterpolationTime); - //m_CurrentInterpValue = target; - //ProcessStates(networkTimeSystem, renderTime, serverTime.FixedDeltaTime, depth); - - - // Only interpolate when there is a start and end point and we have not already reached the end value - if (InterpolateState.Target.HasValue && !IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item)) - { - InterpolateState.CurrentValue = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_SmoothVelocity1, (float)InterpolateState.DeltaTime, deltaTime); - //var timeDelta = Math.Max(0.000000000001, (serverTime.Time - InterpolateState.StartTime)); - //InterpolateState.RelativeTime = Math.Min(InterpolateState.RelativeTime + timeDelta, InterpolateState.DeltaTime); - //InterpolateState.LerpT = (float)(InterpolateState.RelativeTime / InterpolateState.DeltaTime); - - //InterpolateState.PreviousValue = InterpolateState.CurrentValue; - //InterpolateState.CurrentValue = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); - //m_CurrentInterpValue = InterpolateState.LerpT < 1.0f ? Interpolate(m_CurrentInterpValue, InterpolateState.CurrentValue, 0.333333333f) : InterpolateState.CurrentValue; - } - //else - //{ - // TryConsumeFromBuffer(renderTime, serverTime, networkTimeSystem); - //} - TryConsumeFromBuffer(renderTime, serverTime, networkTimeSystem); - m_CurrentInterpValue = InterpolateState.CurrentValue; - //m_CurrentInterpValue = SmoothDamp(m_CurrentInterpValue, InterpolateState.TargetValue, ref m_SmoothVelocity2, serverTime.FixedDeltaTime); - //TryConsumeFromBuffer(renderTime, serverTime, networkTimeSystem); -#if UNITY_EDITOR - LogInfo(networkTimeSystem.ServerTime); -#endif - m_NbItemsReceivedThisFrame = 0; - return m_CurrentInterpValue; - } - - /// - /// Used for internal testing - /// - internal T UpdateInternal(float deltaTime, NetworkTime serverTime) - { - return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); - } - - /// - /// Call to update the state of the interpolators before reading out - /// - /// time since last call - /// our current time - /// current server time - /// The newly interpolated value of type 'T' - public T Update(float deltaTime, double renderTime, double serverTime) - { - TryConsumeFromBuffer(renderTime, serverTime); - - // Only interpolate when there is a start and end point and we have not already reached the end value - if (InterpolateState.Target.HasValue) - { - if (InterpolateState.LerpT < 1.0f) - { - InterpolateState.RelativeTime = Math.Clamp(InterpolateState.RelativeTime + deltaTime, 0.000001f, InterpolateState.Target.Value.TimeSent); - InterpolateState.LerpT = (float)(InterpolateState.RelativeTime / InterpolateState.Target.Value.TimeSent); - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); - if (InterpolateState.LerpT < 1.0f) - { - m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, InterpolateState.CurrentValue, 0.5f); - } - else - { - m_CurrentInterpValue = InterpolateState.CurrentValue; - } - } - } - - m_NbItemsReceivedThisFrame = 0; - return m_CurrentInterpValue; - } - - /// - /// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value". - /// - /// The new measurement value to use - /// The time to record for measurement - public void AddMeasurement(T newMeasurement, double sentTime) - { - m_NbItemsReceivedThisFrame++; - - // 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 - // 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 - if (m_NbItemsReceivedThisFrame > k_BufferCountLimit) - { - if (m_LastBufferedItemReceived.TimeSent < sentTime) - { - m_BufferCount++; - m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); - ResetTo(newMeasurement, sentTime); - // Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues - m_Buffer.Enqueue(m_LastBufferedItemReceived); - } - - return; - } - - if (sentTime > m_LastMeasurementAddedTime) - { - m_BufferCount++; - m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); - m_Buffer.Enqueue(m_LastBufferedItemReceived); - m_LastMeasurementAddedTime = sentTime; - } -#if UNITY_EDITOR - else if (EnableLogging) - { - Debug.Log($"[{m_Name}] Dropping measurement -- Time: {sentTime} Value: {newMeasurement} | Last measurement -- Time: {m_LastMeasurementAddedTime} Value: {m_LastBufferedItemReceived.Item}"); - } -#endif - } - - /// - /// Gets latest value from the interpolator. This is updated every update as time goes by. - /// - /// The current interpolated value of type 'T' - public T GetInterpolatedValue() - { - return m_CurrentInterpValue; - } - - /// - /// Method to override and adapted to the generic type. This assumes interpolation for that value will be clamped. - /// - /// The start value (min) - /// The end value (max) - /// The time value used to interpolate between start and end values (pos) - /// The interpolated value - protected abstract T Interpolate(T start, T end, float time); - - /// - /// Method to override and adapted to the generic type. This assumes interpolation for that value will not be clamped. - /// - /// The start value (min) - /// The end value (max) - /// The time value used to interpolate between start and end values (pos) - /// The interpolated value - protected abstract T InterpolateUnclamped(T start, T end, float time); - } - - /// - /// - /// This is a buffered linear interpolator for a type value - /// - public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator - { - /// - protected override float InterpolateUnclamped(float start, float end, float time) - { - // Disabling Extrapolation: - // TODO: Add Jira Ticket - return Mathf.LerpUnclamped(start, end, time); - } - - protected override float SmoothDamp(float current, float target, ref Vector3 currentVelocity, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) - { - var x = currentVelocity.x; - var retSmooth = Mathf.SmoothDamp(current, target, ref x, duration, maxSpeed, deltaTime); - currentVelocity.x = x; - return retSmooth; - } - - protected override bool IsAproximately(float first, float second, float precision = 1E-07F) - { - return Mathf.Approximately(first, second); - } - - /// - protected override float Interpolate(float start, float end, float time) - { - return Mathf.Lerp(start, end, time); - } - } - - /// - /// - /// This is a buffered linear interpolator for a type value - /// - public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator - { - /// - /// Use when . - /// Use when - /// - /// - /// When using half precision (due to the imprecision) using is - /// less processor intensive (i.e. precision is already "imprecise"). - /// When using full precision (to maintain precision) using is - /// more processor intensive yet yields more precise results. - /// - public bool IsSlerp; - - /// - protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time) - { - if (IsSlerp) - { - return Quaternion.SlerpUnclamped(start, end, time); - } - else - { - return Quaternion.LerpUnclamped(start, end, time); - } - } - - /// - protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time) - { - if (IsSlerp) - { - return Quaternion.Slerp(start, end, time); - } - else - { - return Quaternion.Lerp(start, end, time); - } - } - - protected override Quaternion SmoothDamp(Quaternion current, Quaternion target, ref Vector3 currentVelocity, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) - { - Vector3 currentEuler = current.eulerAngles; - Vector3 targetEuler = target.eulerAngles; - currentEuler.x = Mathf.SmoothDampAngle(currentEuler.x, targetEuler.x, ref currentVelocity.x, duration, maxSpeed, deltaTime); - currentEuler.y = Mathf.SmoothDampAngle(currentEuler.y, targetEuler.y, ref currentVelocity.y, duration, maxSpeed, deltaTime); - currentEuler.z = Mathf.SmoothDampAngle(currentEuler.z, targetEuler.z, ref currentVelocity.z, duration, maxSpeed, deltaTime); - current.eulerAngles = currentEuler; - return current; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected bool Approximately(Quaternion a, Quaternion b, float precision) - { - return Mathf.Abs(a.x - b.x) <= precision && - Mathf.Abs(a.y - b.y) <= precision && - Mathf.Abs(a.z - b.z) <= precision && - Mathf.Abs(a.w - b.w) <= precision; - } - - protected override bool IsAproximately(Quaternion first, Quaternion second, float precision = 1E-07F) - { - return Approximately(first, second, precision); - //return Mathf.Abs(Quaternion.Dot(first, second)) >= 1 - precision; - } - - private Quaternion ConvertToNewTransformSpace(Transform transform, Quaternion rotation, bool inLocalSpace) - { - if (inLocalSpace) - { - return Quaternion.Inverse(transform.rotation) * rotation; - - } - else - { - return transform.rotation * rotation; - } - } - - protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace) - { - var buffer = m_Buffer.ToList(); - m_Buffer.Clear(); - for (int i = 0; i < buffer.Count; i++) - { - var entry = buffer[i]; - entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace); - m_Buffer.Enqueue(entry); - } - InterpolateState.CurrentValue = ConvertToNewTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); - m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); - var end = InterpolateState.Target.Value; - end.Item = ConvertToNewTransformSpace(transform, end.Item, inLocalSpace); - InterpolateState.Target = end; - - base.OnConvertTransformSpace(transform, inLocalSpace); - } - } - - /// - /// A implementation. - /// - public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator - { - /// - /// Use when . - /// Use when - /// - public bool IsSlerp; - /// - protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, float time) - { - if (IsSlerp) - { - return Vector3.SlerpUnclamped(start, end, time); - } - else - { - return Vector3.LerpUnclamped(start, end, time); - } - } - - /// - protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time) - { - if (IsSlerp) - { - return Vector3.Slerp(start, end, time); - } - else - { - return Vector3.Lerp(start, end, time); - } - } - - - private Vector3 ConvertToNewTransformSpace(Transform transform, Vector3 position, bool inLocalSpace) - { - if (inLocalSpace) - { - return transform.InverseTransformPoint(position); - - } - else - { - return transform.TransformPoint(position); - } - } - - protected override Vector3 SmoothDamp(Vector3 current, Vector3 target,ref Vector3 currentVelocity, float duration, float deltaTime, float maxSpeed) - { - return Vector3.SmoothDamp(current, target, ref currentVelocity, duration, maxSpeed, deltaTime); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected bool Approximately(Vector3 a, Vector3 b, float precision) - { - return Math.Round(Mathf.Abs(a.x - b.x), 2) <= precision && - Math.Round(Mathf.Abs(a.y - b.y), 2) <= precision && - Math.Round(Mathf.Abs(a.z - b.z), 2) <= precision; - } - - protected override bool IsAproximately(Vector3 first, Vector3 second, float precision = 1E-07F) - { - return Approximately(first, second, precision); - //return Vector3.Distance(first, second) < precision; - } - - protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace) - { - var buffer = m_Buffer.ToList(); - m_Buffer.Clear(); - for (int i = 0; i < buffer.Count; i++) - { - var entry = buffer[i]; - entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace); - m_Buffer.Enqueue(entry); - } - - InterpolateState.CurrentValue = ConvertToNewTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); - m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); - var end = InterpolateState.Target.Value; - end.Item = ConvertToNewTransformSpace(transform, end.Item, inLocalSpace); - InterpolateState.Target = end; - - base.OnConvertTransformSpace(transform, inLocalSpace); - } + #endregion } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs new file mode 100644 index 0000000000..7429682c49 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs @@ -0,0 +1,42 @@ +using UnityEngine; + +namespace Unity.Netcode +{ + /// + /// + /// This is a buffered linear interpolator for a type value + /// + public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator + { + /// + protected override float InterpolateUnclamped(float start, float end, float time) + { + return Mathf.LerpUnclamped(start, end, time); + } + + /// + protected override float Interpolate(float start, float end, float time) + { + return Mathf.Lerp(start, end, time); + } + + /// + protected internal override bool IsAproximately(float first, float second, float precision = 1E-07F) + { + return Mathf.Approximately(first, second); + } + + /// + protected internal override float SmoothDamp(float current, float target, ref float rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) + { + if (IsAngularValue) + { + return Mathf.SmoothDampAngle(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); + } + else + { + return Mathf.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs.meta new file mode 100644 index 0000000000..e7fb84d836 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 46f1c3f1bf9520b4581097f389f1612e \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs new file mode 100644 index 0000000000..09a94fd2df --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs @@ -0,0 +1,83 @@ +using UnityEngine; + +namespace Unity.Netcode +{ + /// + /// + /// This is a buffered linear interpolator for a type value + /// + public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator + { + /// + /// Use when . + /// Use when + /// + /// + /// When using half precision (due to the imprecision) using is + /// less processor intensive (i.e. precision is already "imprecise"). + /// When using full precision (to maintain precision) using is + /// more processor intensive yet yields more precise results. + /// + public bool IsSlerp; + + /// + protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time) + { + if (IsSlerp) + { + return Quaternion.SlerpUnclamped(start, end, time); + } + else + { + return Quaternion.LerpUnclamped(start, end, time); + } + } + + /// + protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time) + { + if (IsSlerp) + { + return Quaternion.Slerp(start, end, time); + } + else + { + return Quaternion.Lerp(start, end, time); + } + } + + /// + protected internal override Quaternion SmoothDamp(Quaternion current, Quaternion target, ref Quaternion rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) + { + Vector3 currentEuler = current.eulerAngles; + Vector3 targetEuler = target.eulerAngles; + for (int i = 0; i < 3; i++) + { + var velocity = rateOfChange[i]; + currentEuler[i] = Mathf.SmoothDampAngle(currentEuler[i], targetEuler[i], ref velocity, duration, maxSpeed, deltaTime); + rateOfChange[i] = velocity; + } + return Quaternion.Euler(currentEuler); + } + + protected internal override bool IsAproximately(Quaternion first, Quaternion second, float precision) + { + return Mathf.Abs(first.x - second.x) <= precision && + Mathf.Abs(first.y - second.y) <= precision && + Mathf.Abs(first.z - second.z) <= precision && + Mathf.Abs(first.w - second.w) <= precision; + } + + protected internal override Quaternion OnConvertTransformSpace(Transform transform, Quaternion rotation, bool inLocalSpace) + { + if (inLocalSpace) + { + return Quaternion.Inverse(transform.rotation) * rotation; + } + else + { + return transform.rotation * rotation; + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs.meta new file mode 100644 index 0000000000..b064df1cc6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b8ad8124b66f0d54d871841d09fc176b \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs new file mode 100644 index 0000000000..59be639e00 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs @@ -0,0 +1,77 @@ +using UnityEngine; + +namespace Unity.Netcode +{ + /// + /// A implementation. + /// + public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator + { + /// + /// Use when . + /// Use when + /// + public bool IsSlerp; + /// + protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, float time) + { + if (IsSlerp) + { + return Vector3.SlerpUnclamped(start, end, time); + } + else + { + return Vector3.LerpUnclamped(start, end, time); + } + } + + /// + protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time) + { + if (IsSlerp) + { + return Vector3.Slerp(start, end, time); + } + else + { + return Vector3.Lerp(start, end, time); + } + } + + /// + protected internal override Vector3 OnConvertTransformSpace(Transform transform, Vector3 position, bool inLocalSpace) + { + if (inLocalSpace) + { + return transform.InverseTransformPoint(position); + + } + else + { + return transform.TransformPoint(position); + } + } + + /// + protected internal override bool IsAproximately(Vector3 first, Vector3 second, float precision = 0.0001F) + { + return Vector3.Distance(first, second) <= precision; + } + + /// + protected internal override Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 rateOfChange, float duration, float deltaTime, float maxSpeed) + { + if (IsAngularValue) + { + current.x = Mathf.SmoothDampAngle(current.x, target.x, ref rateOfChange.x, duration, maxSpeed, deltaTime); + current.y = Mathf.SmoothDampAngle(current.y, target.y, ref rateOfChange.y, duration, maxSpeed, deltaTime); + current.z = Mathf.SmoothDampAngle(current.z, target.z, ref rateOfChange.z, duration, maxSpeed, deltaTime); + return current; + } + else + { + return Vector3.SmoothDamp(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs.meta new file mode 100644 index 0000000000..311a618f17 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2e7fdc0f62b12c749bab58b047203e12 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 08d7d5d62a..38a7a742f4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3,7 +3,6 @@ using System.Runtime.CompilerServices; using System.Text; using Unity.Mathematics; -using Unity.Netcode.Transports.UTP; using UnityEngine; namespace Unity.Netcode.Components @@ -663,9 +662,6 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade // We use network ticks as opposed to absolute time as the authoritative // side updates on every new tick. BytePacker.WriteValueBitPacked(m_Writer, NetworkTick); - - serializer.SerializeValue(ref TickOffset); - } else { @@ -673,8 +669,6 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade // We use network ticks as opposed to absolute time as the authoritative // side updates on every new tick. ByteUnpacker.ReadValueBitPacked(m_Reader, out NetworkTick); - - serializer.SerializeValue(ref TickOffset); } } @@ -943,6 +937,28 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #endregion #region PROPERTIES AND GENERAL METHODS + + /// + /// The two ways to smooth during interpolation. + /// + public enum InterpolationTypes + { + /// + /// Uses lerping and yields a linear progression between two values. + /// + Lerp, + /// + /// Uses a smooth dampening approach and is recommended when is enabled. + /// + SmoothDampening + } + + public InterpolationTypes InterpolationType; + + public float PositionMaxInterpolationTime = 0.1f; + public float RotationMaxInterpolationTime = 0.1f; + public float ScaleMaxInterpolationTime = 0.1f; + public enum AuthorityModes { Server, @@ -1791,11 +1807,11 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var positionThreshold = Vector3.one * PositionThreshold; var rotationThreshold = Vector3.one * RotAngleThreshold; - //if (m_UseRigidbodyForMotion) - //{ - // positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold(); - // rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold(); - //} + if (m_UseRigidbodyForMotion) + { + positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold(); + rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold(); + } #else var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position; var rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; @@ -2168,7 +2184,6 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra { // We use the NetworkTickSystem version since ServerTime is set when updating ticks networkState.NetworkTick = m_CachedNetworkManager.NetworkTickSystem.ServerTime.Tick; - networkState.TickOffset = (float)m_CachedNetworkManager.NetworkTickSystem.ServerTime.TickOffset; } } @@ -2887,7 +2902,7 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf } // Get the time when this new state was sent - newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkTickSystem.TickRate, newState.NetworkTick, newState.TickOffset).Time; + newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkTickSystem.TickRate, newState.NetworkTick).Time; if (LogStateUpdate) { @@ -2958,11 +2973,10 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf /// (i.e. Position, Scale, and Rotation) ///
/// Maximum time boundary that can be used in a frame when interpolating between two values + [Obsolete("This method is no longer valid.", false)] public void SetMaxInterpolationBound(float maxInterpolationBound) { - m_RotationInterpolator.MaxInterpolationBound = maxInterpolationBound; - m_PositionInterpolator.MaxInterpolationBound = maxInterpolationBound; - m_ScaleInterpolator.MaxInterpolationBound = maxInterpolationBound; + // No longer valid } /// @@ -3143,12 +3157,22 @@ protected internal override void InternalOnNetworkPostSpawn() base.InternalOnNetworkPostSpawn(); } + /// + /// For testing purposes to quickly change the default from Lerp to SmoothDamp + /// + internal static bool AssignDefaultInterpolationType; + internal static InterpolationTypes DefaultInterpolationType; + /// /// Create interpolators when first instantiated to avoid memory allocations if the /// associated NetworkObject persists (i.e. despawned but not destroyed or pools) /// protected virtual void Awake() { + if (AssignDefaultInterpolationType) + { + InterpolationType = DefaultInterpolationType; + } // Rotation is a single Quaternion since each Euler axis will affect the quaternion's final value m_RotationInterpolator = new BufferedLinearInterpolatorQuaternion(); m_PositionInterpolator = new BufferedLinearInterpolatorVector3(); @@ -3697,59 +3721,83 @@ public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newSca #region UPDATES AND AUTHORITY CHECKS private NetworkTransformTickRegistration m_NetworkTransformTickRegistration; + + // Non-Authority private void UpdateInterpolation() { - // Non-Authority - if (Interpolate) - { - AdjustForChangeInTransformSpace(); + AdjustForChangeInTransformSpace(); - var serverTime = m_CachedNetworkManager.ServerTime; - var cachedServerTime = serverTime.Time; - // var offset = (float)serverTime.TickOffset; + var cachedServerTime = m_CachedNetworkManager.ServerTime.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - //var cachedDeltaTime = m_UseRigidbodyForMotion ? m_CachedNetworkManager.RealTimeProvider.FixedDeltaTime : m_CachedNetworkManager.RealTimeProvider.DeltaTime; - var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; + var cachedDeltaTime = m_UseRigidbodyForMotion ? Time.fixedDeltaTime : Time.deltaTime; #else - var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; + var cachedDeltaTime = Time.deltaTime; #endif + var tickLatency = m_CachedNetworkManager.NetworkTimeSystem.TickLatency; + + // If using an owner authoritative motion model + if (!IsServerAuthoritative()) + { + // and if we are in a client-server topology (including DAHost) + if (!m_CachedNetworkManager.DistributedAuthorityMode || + (m_CachedNetworkManager.DistributedAuthorityMode && !m_CachedNetworkManager.CMBServiceConnection)) + { + // If this instance belongs to another client (i.e. not the server/host), then add 1 to our tick latency. + if (!m_CachedNetworkManager.IsServer && !NetworkObject.IsOwnedByServer) + { + // Account for the 2xRTT with owner authoritative + tickLatency += 1; + } + } + } - //var cachedDeltaTime = Time.deltaTime; - // With owner authoritative mode, non-authority clients can lag behind - // by more than 1 tick period of time. The current "solution" for now - // is to make their cachedRenderTime run 2 ticks behind. + var tickLatencyAsTime = m_CachedNetworkManager.LocalTime.TimeTicksAgo(tickLatency).Time; + var minDeltaTime = m_CachedNetworkManager.LocalTime.FixedDeltaTime; + var maxDeltaTime = 1.0f + (tickLatency * m_CachedNetworkManager.LocalTime.FixedDeltaTime); - // TODO: This could most likely just always be 2 - //var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1; - //var cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time; - var cachedRenderTime = serverTime.TimeTicksAgo(2).Time; + var useLerp = InterpolationType == InterpolationTypes.Lerp; - // Now only update the interpolators for the portions of the transform being synchronized - if (SynchronizePosition) + // Now only update the interpolators for the portions of the transform being synchronized + if (SynchronizePosition) + { + if (useLerp) { - //m_PositionInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); -#if UNITY_EDITOR - m_PositionInterpolator.NetworkObjectId = NetworkObjectId; - m_PositionInterpolator.NetworkBehaviourId = NetworkBehaviourId; - m_PositionInterpolator.EnableLogging = false; -#endif - m_PositionInterpolator.Update(cachedDeltaTime, m_CachedNetworkManager.ServerTime, m_CachedNetworkManager.NetworkTimeSystem); + m_PositionInterpolator.MaximumInterpolationTime = PositionMaxInterpolationTime; + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); } + else + { + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); + } + } - if (SynchronizeRotation) + if (SynchronizeRotation) + { + if (useLerp) { + m_RotationInterpolator.MaximumInterpolationTime = RotationMaxInterpolationTime; // When using half precision Lerp towards the target rotation. // When using full precision Slerp towards the target rotation. /// m_RotationInterpolator.IsSlerp = !UseHalfFloatPrecision; - //m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); - m_RotationInterpolator.Update(cachedDeltaTime, m_CachedNetworkManager.ServerTime, m_CachedNetworkManager.NetworkTimeSystem); + m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + else + { + m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); } + } - if (SynchronizeScale) + if (SynchronizeScale) + { + if (useLerp) + { + m_ScaleInterpolator.MaximumInterpolationTime = ScaleMaxInterpolationTime; + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + else { - //m_ScaleInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); - m_ScaleInterpolator.Update(cachedDeltaTime, m_CachedNetworkManager.ServerTime, m_CachedNetworkManager.NetworkTimeSystem); + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); } } } @@ -3772,26 +3820,17 @@ public virtual void OnUpdate() } // Update interpolation - UpdateInterpolation(); + if (Interpolate) + { + UpdateInterpolation(); + } + // Apply the current authoritative state ApplyAuthoritativeState(); } #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D - - internal void OnInterpolateFixedUpdate() - { - // If not spawned or this instance has authority, exit early - if (!m_UseRigidbodyForMotion || !IsSpawned || CanCommitToTransform) - { - return; - } - - // Update interpolation - UpdateInterpolation(); - } - /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . @@ -3806,6 +3845,13 @@ public virtual void OnFixedUpdate() m_NetworkRigidbodyInternal.WakeIfSleeping(); + + // Update interpolation when enabled + if (Interpolate) + { + UpdateInterpolation(); + } + // Apply the current authoritative state ApplyAuthoritativeState(); } @@ -3964,17 +4010,20 @@ private void UpdateTransformState() internal static float GetTickLatency(NetworkManager networkManager) { - if (s_NetworkTickRegistration.ContainsKey(networkManager)) + if (networkManager.IsListening) { - return s_NetworkTickRegistration[networkManager].TicksAgo; + return (float)(networkManager.NetworkTimeSystem.TickLatency + networkManager.LocalTime.TickOffset); } - return 0f; + return 0; } /// /// Returns the number of ticks (fractional) a client is latent relative - /// to its current RTT. + /// to its current averaged RTT. /// + /// + /// Only valid on clients. + /// public static float GetTickLatency() { return GetTickLatency(NetworkManager.Singleton); @@ -3982,9 +4031,9 @@ public static float GetTickLatency() internal static float GetTickLatencyInSeconds(NetworkManager networkManager) { - if (s_NetworkTickRegistration.ContainsKey(networkManager)) + if (networkManager.IsListening) { - return s_NetworkTickRegistration[networkManager].TicksAgoInSeconds(); + return (float)networkManager.LocalTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency).Time; } return 0f; } @@ -4026,40 +4075,12 @@ public void Remove() RemoveTickUpdate(m_NetworkManager); } - internal float TicksAgoInSeconds() - { - return 2 * m_TickFrequency; - // TODO: We need an RTT that updates regularly and not just when the client sends packets - // return Mathf.Max(1.0f, TicksAgo) * m_TickFrequency; - } - /// /// Invoked once per network tick, this will update any registered /// authority instances. /// private void TickUpdate() { - // TODO: We need an RTT that updates regularly and not just when the client sends packets - // if (m_UnityTransport != null) - // { - // // Determine the desired ticks ago by the RTT (this really should be the combination of the - // // authority and non-authority 1/2 RTT but in the end anything beyond 300ms is considered very poor - // // network quality so latent interpolation is going to be expected). - // var rtt = Mathf.Max(m_TickInMS, m_UnityTransport.GetCurrentRtt(NetworkManager.ServerClientId)); - // m_TicksAgoSamples[m_TickSampleIndex] = Mathf.Max(1, (int)(rtt * m_TickFrequency)); - // var tickAgoSum = 0.0f; - // foreach (var tickAgo in m_TicksAgoSamples) - // { - // tickAgoSum += tickAgo; - // } - // m_PreviousTicksAgo = TicksAgo; - // TicksAgo = Mathf.Lerp(m_PreviousTicksAgo, tickAgoSum / m_TickRate, m_TickFrequency); - // m_TickSampleIndex = (m_TickSampleIndex + 1) % m_TickRate; - // // Get the partial tick value for when this is all calculated to provide an offset for determining - // // the relative starting interpolation point for the next update - // Offset = m_OffsetTickFrequency * (Mathf.Max(2, TicksAgo) - (int)TicksAgo); - // } - // TODO FIX: The local NetworkTickSystem can invoke with the same network tick as before if (m_NetworkManager.ServerTime.Tick <= m_LastTick) { @@ -4074,40 +4095,11 @@ private void TickUpdate() } m_LastTick = m_NetworkManager.ServerTime.Tick; } - - - private UnityTransport m_UnityTransport; - private float m_TickFrequency; - // private float m_OffsetTickFrequency; - // private ulong m_TickInMS; - // private int m_TickSampleIndex; - private int m_TickRate; - public float TicksAgo { get; private set; } - // public float Offset { get; private set; } - // private float m_PreviousTicksAgo; - - private List m_TicksAgoSamples = new List(); - public NetworkTransformTickRegistration(NetworkManager networkManager) { m_NetworkManager = networkManager; m_NetworkTickUpdate = new Action(TickUpdate); networkManager.NetworkTickSystem.Tick += m_NetworkTickUpdate; - m_TickRate = (int)m_NetworkManager.NetworkConfig.TickRate; - m_TickFrequency = 1.0f / m_TickRate; - //// For the offset, it uses the fractional remainder of the tick to determine the offset. - //// In order to keep within tick boundaries, we increment the tick rate by 1 to assure it - //// will always be < the tick frequency. - // m_OffsetTickFrequency = 1.0f / (m_TickRate + 1); - // m_TickInMS = (ulong)(1000 * m_TickFrequency); - // m_UnityTransport = m_NetworkManager.NetworkConfig.NetworkTransport as UnityTransport; - //// Fill the sample with a starting value of 1 - // for (int i = 0; i < m_TickRate; i++) - // { - // m_TicksAgoSamples.Add(1f); - // } - TicksAgo = 2f; - // m_PreviousTicksAgo = 1f; if (networkManager.IsServer) { networkManager.OnServerStopped += OnNetworkManagerStopped; From 4d691dc6bdd72d404c8b3ec6aaf7913ad7efb6ac Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 4 Mar 2025 15:01:28 -0600 Subject: [PATCH 09/41] update Minor fixes for recent changes. --- .../Runtime/Components/RigidbodyContactEventManager.cs | 4 ++++ .../Tests/Runtime/TransformInterpolationTests.cs | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index 02f9c98e7e..b2dcb94efe 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -119,6 +119,10 @@ private void OnEnable() public void RegisterHandler(IContactEventHandler contactEventHandler, bool register = true) { var rigidbody = contactEventHandler.GetRigidbody(); + if (rigidbody == null) + { + return; + } var instanceId = rigidbody.GetInstanceID(); if (register) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 4e0cea8e53..982f3f4116 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -43,7 +43,6 @@ protected override void OnInitialize(ref NetworkTransformState replicatedState) m_LocalSpaceToggles = 0; m_FrameRateFractional = 1.0f / Application.targetFrameRate; PositionThreshold = MinThreshold; - SetMaxInterpolationBound(1.0f); base.OnInitialize(ref replicatedState); } From a8a51e27d79b335ebca8dd2fe59d9d8cf55d4362 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 4 Mar 2025 19:11:59 -0600 Subject: [PATCH 10/41] update some clean up from wip. --- .../BufferedLinearInterpolator.cs | 74 ++++++++++--------- .../Runtime/Components/NetworkTransform.cs | 2 +- .../Runtime/Core/NetworkManager.cs | 39 ---------- .../Runtime/Timing/NetworkTimeSystem.cs | 2 +- 4 files changed, 40 insertions(+), 77 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index e72b350899..5ce569badc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -36,7 +36,6 @@ internal struct CurrentState public float DeltaTime; public float LerpT; - public T TargetValue; public T CurrentValue; public T PreviousValue; @@ -45,7 +44,7 @@ internal struct CurrentState public float AverageDeltaTime => m_AverageDeltaTime; public float FinalTimeToTarget => TimeToTargetValue - DeltaTime; - public bool AddDeltaTime(float deltaTime) + public void AddDeltaTime(float deltaTime) { if (m_AverageDeltaTime == 0.0f) { @@ -53,15 +52,10 @@ public bool AddDeltaTime(float deltaTime) } else { - // Gradually adjust our delta time to keep this - // value more consistent - m_AverageDeltaTime = 3.0f * m_AverageDeltaTime; m_AverageDeltaTime += deltaTime; - m_AverageDeltaTime *= 0.25f; } DeltaTime = Math.Max(DeltaTime + m_AverageDeltaTime, TimeToTargetValue); LerpT = DeltaTime / TimeToTargetValue; - return FinalTimeToTarget <= m_AverageDeltaTime; } public bool TargetTimeAproximatelyReached() @@ -70,7 +64,7 @@ public bool TargetTimeAproximatelyReached() { return false; } - return (m_AverageDeltaTime * 0.3333333f) >= FinalTimeToTarget; + return m_AverageDeltaTime >= FinalTimeToTarget; } public void Reset(T currentValue) @@ -78,7 +72,6 @@ public void Reset(T currentValue) Target = null; CurrentValue = currentValue; PreviousValue = currentValue; - TargetValue = currentValue; // When reset, we consider ourselves to have already arrived at the target (even if no target is set) LerpT = 1.0f; RelativeTime = 0.0; @@ -147,22 +140,6 @@ public void Reset(T currentValue) private bool m_IsAngularValue; protected bool IsAngularValue => m_IsAngularValue; - internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) - { - var count = m_Buffer.Count; - for (int i = 0; i < count; i++) - { - var entry = m_Buffer.Dequeue(); - entry.Item = OnConvertTransformSpace(transform, entry.Item, inLocalSpace); - m_Buffer.Enqueue(entry); - } - InterpolateState.CurrentValue = OnConvertTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); - var end = InterpolateState.Target.Value; - end.Item = OnConvertTransformSpace(transform, end.Item, inLocalSpace); - InterpolateState.Target = end; - InLocalSpace = inLocalSpace; - } - /// /// Resets interpolator to the defaults. /// @@ -187,7 +164,7 @@ public void Clear() /// The current server time /// Defaults to true and is the recommened way to achieve a smoother interpolation between buffer item values. /// When rotation is expressed as Euler values (i.e. Vector3 and/or float) this helps determine what kind of smooth dampening to use. - public void ResetTo(T targetValue, double serverTime, bool useSmoothDampening = true, bool isAngularValue = false) + public void ResetTo(T targetValue, double serverTime, bool isAngularValue = false) { #if UNITY_EDITOR m_Name = GetType().Name; @@ -198,8 +175,6 @@ public void ResetTo(T targetValue, double serverTime, bool useSmoothDampening = InterpolateState.Reset(targetValue); // TODO: If we get single lerping working, then m_CurrentInterpValue is no longer needed. m_CurrentInterpValue = targetValue; - - m_UseSmoothDamening = useSmoothDampening; m_IsAngularValue = isAngularValue; // Add the first measurement for our baseline @@ -212,12 +187,16 @@ public void ResetTo(T targetValue, double serverTime, bool useSmoothDampening = /// render time: the time in "ticks ago" relative to the current tick latency /// minimum time delta (defaults to tick frequency) /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer - private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime) + private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime, bool isSmoothed = false) { - if (!InterpolateState.Target.HasValue || - (InterpolateState.Target.Value.TimeSent <= renderTime && - (InterpolateState.TargetTimeAproximatelyReached() || - IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item)))) + var canGetNextItem = true; + + if (isSmoothed && InterpolateState.Target.HasValue) + { + canGetNextItem = InterpolateState.TargetTimeAproximatelyReached() || IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); + } + + if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime && canGetNextItem)) { BufferedItem? previousItem = null; var startTime = 0.0; @@ -260,7 +239,14 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m } // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated // for each item as opposed to losing the resolution of the values. - InterpolateState.TimeToTargetValue = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); + if (isSmoothed) + { + InterpolateState.TimeToTargetValue = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); + } + else + { + InterpolateState.TimeToTargetValue = (float)(target.TimeSent - startTime); + } InterpolateState.Target = target; } InterpolateState.DeltaTime = 0.0f; @@ -299,12 +285,12 @@ internal T UpdateInternal(float deltaTime, NetworkTime serverTime) /// The newly interpolated value of type 'T' public T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, float maxDeltaTime) { - TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime); + TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime, true); // Only interpolate when there is a start and end point and we have not already reached the end value if (InterpolateState.Target.HasValue) { InterpolateState.AddDeltaTime(deltaTime); - InterpolateState.CurrentValue = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime, 10000.0f); + InterpolateState.CurrentValue = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, deltaTime); } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; @@ -439,6 +425,22 @@ protected internal virtual T OnConvertTransformSpace(Transform transform, T item return default; } + internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) + { + var count = m_Buffer.Count; + for (int i = 0; i < count; i++) + { + var entry = m_Buffer.Dequeue(); + entry.Item = OnConvertTransformSpace(transform, entry.Item, inLocalSpace); + m_Buffer.Enqueue(entry); + } + InterpolateState.CurrentValue = OnConvertTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); + var end = InterpolateState.Target.Value; + end.Item = OnConvertTransformSpace(transform, end.Item, inLocalSpace); + InterpolateState.Target = end; + InLocalSpace = inLocalSpace; + } + // TODO: Collect data points so a single buffered linear interpolator can provide additional data points // to be visualized in RNSM. #region DEBUG_LOGGING diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 38a7a742f4..21ca56ab9b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3753,7 +3753,7 @@ private void UpdateInterpolation() var tickLatencyAsTime = m_CachedNetworkManager.LocalTime.TimeTicksAgo(tickLatency).Time; var minDeltaTime = m_CachedNetworkManager.LocalTime.FixedDeltaTime; - var maxDeltaTime = 1.0f + (tickLatency * m_CachedNetworkManager.LocalTime.FixedDeltaTime); + var maxDeltaTime = (tickLatency * m_CachedNetworkManager.ServerTime.FixedDeltaTime); var useLerp = InterpolationType == InterpolationTypes.Lerp; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index afef5c4b0f..9f257fb5e1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -333,25 +333,6 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) MessageManager.CleanupDisconnectedClients(); AnticipationSystem.ProcessReanticipation(); - - //foreach (var networkObjectEntry in NetworkTransformFixedUpdate) - //{ - // // if not active or not spawned then skip - // if (!networkObjectEntry.Value.gameObject.activeInHierarchy || !networkObjectEntry.Value.IsSpawned) - // { - // continue; - // } - - // foreach (var networkTransformEntry in networkObjectEntry.Value.NetworkTransforms) - // { - // // only update if enabled - // if (networkTransformEntry.enabled) - // { - // networkTransformEntry.OnInterpolateFixedUpdate(); - // networkTransformEntry.OnFixedUpdate(); - // } - // } - //} } break; #if COM_UNITY_MODULES_PHYSICS @@ -370,7 +351,6 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) // only update if enabled if (networkTransformEntry.enabled) { - networkTransformEntry.OnInterpolateFixedUpdate(); networkTransformEntry.OnFixedUpdate(); } } @@ -382,25 +362,6 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) { NetworkTimeSystem.UpdateTime(); AnticipationSystem.Update(); - - //foreach (var networkObjectEntry in NetworkTransformFixedUpdate) - //{ - // // if not active or not spawned then skip - // if (!networkObjectEntry.Value.gameObject.activeInHierarchy || !networkObjectEntry.Value.IsSpawned) - // { - // continue; - // } - - // foreach (var networkTransformEntry in networkObjectEntry.Value.NetworkTransforms) - // { - // // only update if enabled - // if (networkTransformEntry.enabled) - // { - // networkTransformEntry.OnInterpolateFixedUpdate(); - // //networkTransformEntry.OnFixedUpdate(); - // } - // } - //} } break; case NetworkUpdateStage.PreLateUpdate: diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index 1a26d555f6..0f8f87c414 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -223,7 +223,7 @@ public bool Advance(double deltaTimeSec) if (LastSyncedRttSec > 0.0f) { m_TickLatencyAverage = Mathf.Lerp(m_TickLatencyAverage, (float)((LastSyncedRttSec + deltaTimeSec) / m_NetworkTickSystem.ServerTime.FixedDeltaTimeAsDouble), (float)deltaTimeSec); - TickLatency = (int)Mathf.Max(1.0f, Mathf.Round(m_TickLatencyAverage)) + 1; + TickLatency = (int)Mathf.Max(1.0f, Mathf.Round(m_TickLatencyAverage)); } if (Math.Abs(m_DesiredLocalTimeOffset - m_CurrentLocalTimeOffset) > HardResetThresholdSec || Math.Abs(m_DesiredServerTimeOffset - m_CurrentServerTimeOffset) > HardResetThresholdSec) From de3094f1a4eddc20464e82d7a775b6b7a7bbe208 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 4 Mar 2025 19:31:40 -0600 Subject: [PATCH 11/41] fix Fixing PVP issues. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 6 ++---- com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 5ce569badc..c01394a4cb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -155,14 +155,12 @@ public void Clear() /// /// Resets the current interpolator to the target valueTeleports current interpolation value to targetValue. - /// /// /// /// This is used when first synchronizing/initializing and when telporting an object. /// /// The target value to reset the interpolator to - /// The current server time - /// Defaults to true and is the recommened way to achieve a smoother interpolation between buffer item values. + /// The current server time /// When rotation is expressed as Euler values (i.e. Vector3 and/or float) this helps determine what kind of smooth dampening to use. public void ResetTo(T targetValue, double serverTime, bool isAngularValue = false) { @@ -409,7 +407,7 @@ protected internal virtual T SmoothDamp(T current, T target, ref T rateOfChange, /// First value of type . /// Second value of type . /// The precision of the aproximation. - /// + /// true if the two values are aproximately the same and false if they are not protected internal virtual bool IsAproximately(T first, T second, float precision = k_AproximatePrecision) { return false; diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs index 083956aa84..c37c29fa75 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs @@ -49,7 +49,7 @@ public struct NetworkTime /// /// /// This could result in a potential floating point precision variance on different systems.
- /// See for a more precise value. + /// See for a more precise value. ///
public float FixedDeltaTime => (float)m_TickInterval; From 15c8e59d465c5f4e4a9f23af966a47abbba90dda Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 4 Mar 2025 19:37:33 -0600 Subject: [PATCH 12/41] update Adding XML API documentation to some new NetworkTransform properties. --- .../Runtime/Components/NetworkTransform.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 21ca56ab9b..7e4f22b56f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -114,8 +114,6 @@ internal uint BitSet // Used for NetworkDeltaPosition delta position synchronization internal int NetworkTick; - internal float TickOffset; - // Used when tracking by state ID is enabled internal int StateId; @@ -953,10 +951,25 @@ public enum InterpolationTypes SmoothDampening } + /// + /// The interpolation type to use for the instance. + /// + /// + /// For more details review . + /// public InterpolationTypes InterpolationType; + /// + /// When is enabled and using , this adjust the maximum interpolation time for position. + /// public float PositionMaxInterpolationTime = 0.1f; + /// + /// When is enabled and using , this adjust the maximum interpolation time for rotation. + /// public float RotationMaxInterpolationTime = 0.1f; + /// + /// When is enabled and using , this adjust the maximum interpolation time for scale. + /// public float ScaleMaxInterpolationTime = 0.1f; public enum AuthorityModes From 283ab9fb62463b95babae899edae4dd1d588bc3d Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 4 Mar 2025 19:48:56 -0600 Subject: [PATCH 13/41] fix More PVP fun --- .../BufferedLinearInterpolator.cs | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index c01394a4cb..7c4c67876a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -13,6 +13,9 @@ public abstract class BufferedLinearInterpolator where T : struct { private const float k_AproximatePrecision = 0.0001f; + /// + /// Represents a buffered item measurement. + /// protected internal struct BufferedItem { public int ItemId; @@ -117,7 +120,6 @@ public void Reset(T currentValue) private BufferedItem m_LastBufferedItemReceived; private int m_NbItemsReceivedThisFrame; - protected internal T m_CurrentInterpValue; private double m_LastMeasurementAddedTime = 0.0f; internal bool EndOfBuffer => m_Buffer.Count == 0; @@ -127,6 +129,10 @@ public void Reset(T currentValue) /// The current interpolation state ///
internal CurrentState InterpolateState; + + /// + /// The current buffered items received by the authority. + /// protected internal readonly Queue m_Buffer = new Queue(k_BufferCountLimit); /// @@ -134,10 +140,11 @@ public void Reset(T currentValue) /// private T m_RateOfChange; - private bool m_UseSmoothDamening; - protected bool UseSmoothDampening => m_UseSmoothDamening; - private bool m_IsAngularValue; + + /// + /// When true, the value is an angular numeric representation. + /// protected bool IsAngularValue => m_IsAngularValue; /// @@ -147,7 +154,6 @@ public void Clear() { m_Buffer.Clear(); m_BufferCount = 0; - m_CurrentInterpValue = default; m_LastMeasurementAddedTime = 0.0; InterpolateState.Reset(default); m_RateOfChange = default; @@ -171,8 +177,6 @@ public void ResetTo(T targetValue, double serverTime, bool isAngularValue = fals Clear(); // Set our initial value InterpolateState.Reset(targetValue); - // TODO: If we get single lerping working, then m_CurrentInterpValue is no longer needed. - m_CurrentInterpValue = targetValue; m_IsAngularValue = isAngularValue; // Add the first measurement for our baseline @@ -416,8 +420,10 @@ protected internal virtual bool IsAproximately(T first, T second, float precisio /// /// Converts a value of type from world to local space or vice versa. /// - /// Reference transform - /// local or world space (true or false) + /// Reference transform. + /// The item to convert. + /// local or world space (true or false). + /// The converted value. protected internal virtual T OnConvertTransformSpace(Transform transform, T item, bool inLocalSpace) { return default; From e1f67631c25ff5b22ba1ba2cab8a5814a324938e Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 4 Mar 2025 20:54:24 -0600 Subject: [PATCH 14/41] fix More and more PVP fun. --- .../Interpolator/BufferedLinearInterpolator.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 7c4c67876a..e1e28e6865 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -18,10 +18,25 @@ public abstract class BufferedLinearInterpolator where T : struct /// protected internal struct BufferedItem { + /// + /// THe item identifier + /// public int ItemId; + /// + /// The item value + /// public T Item; + /// + /// The time the item was sent. + /// public double TimeSent; + /// + /// The constructor + /// + /// The item value. + /// The time the item was sent. + /// The item identifier public BufferedItem(T item, double timeSent, int itemId) { Item = item; From 7b770a4fbfd0b6b4be5c1a9acf0badd1748722f0 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 4 Mar 2025 21:29:29 -0600 Subject: [PATCH 15/41] fix Missed two PVP issues. --- .../Interpolator/BufferedLinearInterpolatorQuaternion.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs index 09a94fd2df..532f7dc286 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs @@ -60,6 +60,7 @@ protected internal override Quaternion SmoothDamp(Quaternion current, Quaternion return Quaternion.Euler(currentEuler); } + /// protected internal override bool IsAproximately(Quaternion first, Quaternion second, float precision) { return Mathf.Abs(first.x - second.x) <= precision && @@ -68,6 +69,7 @@ protected internal override bool IsAproximately(Quaternion first, Quaternion sec Mathf.Abs(first.w - second.w) <= precision; } + /// protected internal override Quaternion OnConvertTransformSpace(Transform transform, Quaternion rotation, bool inLocalSpace) { if (inLocalSpace) From 6562b9014c0e670d5eecb30e1a1aadf82a87c361 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Tue, 4 Mar 2025 21:41:19 -0600 Subject: [PATCH 16/41] test Adding Lerp and SmoothDampening to TestFixutres. --- .../NetworkTransform/NetworkTransformTests.cs | 70 +++++++++++++------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 7e12934a67..6716b980d9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -1,5 +1,6 @@ using System.Collections; using NUnit.Framework; +using Unity.Netcode.Components; using UnityEngine; namespace Unity.Netcode.RuntimeTests @@ -9,29 +10,51 @@ namespace Unity.Netcode.RuntimeTests /// server and host operating modes and will test both authoritative /// models for each operating mode. ///
- [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.DAHost, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + #endif - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Server, Authority.ServerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] #endif - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] #if !MULTIPLAYER_TOOLS - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full)] - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.Lerp)] + + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Euler, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.None, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Full, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, RotationCompression.QuaternionCompress, Rotation.Quaternion, Precision.Half, NetworkTransform.InterpolationTypes.SmoothDampening)] #endif internal class NetworkTransformTests : NetworkTransformBase { @@ -41,9 +64,12 @@ internal class NetworkTransformTests : NetworkTransformBase ///
/// Determines if we are running as a server or host /// Determines if we are using server or owner authority - public NetworkTransformTests(HostOrServer testWithHost, Authority authority, RotationCompression rotationCompression, Rotation rotation, Precision precision) : + public NetworkTransformTests(HostOrServer testWithHost, Authority authority, RotationCompression rotationCompression, Rotation rotation, Precision precision, NetworkTransform.InterpolationTypes interpolation) : base(testWithHost, authority, rotationCompression, rotation, precision) - { } + { + NetworkTransform.AssignDefaultInterpolationType = true; + NetworkTransform.DefaultInterpolationType = interpolation; + } protected override bool m_EnableTimeTravel => true; protected override bool m_SetupIsACoroutine => false; @@ -102,7 +128,7 @@ private void AllChildrenLocalTransformValuesMatch(bool useSubChild, ChildrenTran #if !MULTIPLAYER_TOOLS - private void UpdateTransformLocal(Components.NetworkTransform networkTransformTestComponent) + private void UpdateTransformLocal(NetworkTransform networkTransformTestComponent) { networkTransformTestComponent.transform.localPosition += GetRandomVector3(0.5f, 2.0f); var rotation = networkTransformTestComponent.transform.localRotation; @@ -112,7 +138,7 @@ private void UpdateTransformLocal(Components.NetworkTransform networkTransformTe networkTransformTestComponent.transform.localRotation = rotation; } - private void UpdateTransformWorld(Components.NetworkTransform networkTransformTestComponent) + private void UpdateTransformWorld(NetworkTransform networkTransformTestComponent) { networkTransformTestComponent.transform.position += GetRandomVector3(0.5f, 2.0f); var rotation = networkTransformTestComponent.transform.rotation; From 1cf4f3d851ec245e86ee528890ae636cc9f8324c Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 5 Mar 2025 15:24:22 -0600 Subject: [PATCH 17/41] fix Putting some things back and realized I should adhere to the original buffered linear interpolator logic/pattern when using Lerp for interpolation to assure there is no impact on users with this update. --- .../BufferedLinearInterpolator.cs | 348 ++++++++---------- .../Runtime/Components/NetworkTransform.cs | 5 +- .../Runtime/Timing/NetworkTimeSystem.cs | 4 +- 3 files changed, 150 insertions(+), 207 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index e1e28e6865..0121665a9d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -49,7 +49,7 @@ internal struct CurrentState public BufferedItem? Target; public double StartTime; - public double RelativeTime; + public double EndTime; public float TimeToTargetValue; public float DeltaTime; public float LerpT; @@ -72,8 +72,14 @@ public void AddDeltaTime(float deltaTime) { m_AverageDeltaTime += deltaTime; } - DeltaTime = Math.Max(DeltaTime + m_AverageDeltaTime, TimeToTargetValue); - LerpT = DeltaTime / TimeToTargetValue; + DeltaTime = Math.Min(DeltaTime + m_AverageDeltaTime, TimeToTargetValue); + LerpT = TimeToTargetValue == 0.0f ? 1.0f : DeltaTime / TimeToTargetValue; + } + + public void ResetDelta() + { + m_AverageDeltaTime = 0.0f; + DeltaTime = 0.0f; } public bool TargetTimeAproximatelyReached() @@ -92,9 +98,9 @@ public void Reset(T currentValue) PreviousValue = currentValue; // When reset, we consider ourselves to have already arrived at the target (even if no target is set) LerpT = 1.0f; - RelativeTime = 0.0; - DeltaTime = 0.0f; - m_AverageDeltaTime = 0.0f; + EndTime = 0.0; + StartTime = 0.0; + ResetDelta(); } } @@ -125,11 +131,19 @@ public void Reset(T currentValue) // that we don't have a very small buffer because of this. private const int k_BufferCountLimit = 100; + private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator + + /// /// There's two factors affecting interpolation: buffering (set in NetworkManager's NetworkTimeSystem) and interpolation time, which is the amount of time it'll take to reach the target. This is to affect the second one. /// public float MaximumInterpolationTime = 0.1f; + /// + /// The maximum Lerp "t" boundary when using standard lerping for interpolation + /// + internal float MaxInterpolationBound = 3.0f; + private int m_BufferCount; private BufferedItem m_LastBufferedItemReceived; @@ -185,33 +199,33 @@ public void Clear() /// When rotation is expressed as Euler values (i.e. Vector3 and/or float) this helps determine what kind of smooth dampening to use. public void ResetTo(T targetValue, double serverTime, bool isAngularValue = false) { -#if UNITY_EDITOR - m_Name = GetType().Name; -#endif - // Clear everything first - Clear(); + InternalReset(targetValue, serverTime, isAngularValue); + } + + private void InternalReset(T targetValue, double serverTime, bool isAngularValue = false, bool addMeasurement = true) + { + m_RateOfChange = default; // Set our initial value InterpolateState.Reset(targetValue); m_IsAngularValue = isAngularValue; - // Add the first measurement for our baseline - AddMeasurement(targetValue, serverTime); + if (addMeasurement) + { + // Add the first measurement for our baseline + AddMeasurement(targetValue, serverTime); + } } + #region Smooth Dampening Interpolation /// /// TryConsumeFromBuffer: Smooth Dampening Version /// /// render time: the time in "ticks ago" relative to the current tick latency /// minimum time delta (defaults to tick frequency) /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer - private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime, bool isSmoothed = false) + private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime) { - var canGetNextItem = true; - - if (isSmoothed && InterpolateState.Target.HasValue) - { - canGetNextItem = InterpolateState.TargetTimeAproximatelyReached() || IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); - } + var canGetNextItem = InterpolateState.TargetTimeAproximatelyReached() || IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime && canGetNextItem)) { @@ -233,7 +247,7 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m // this scenario, we just want to continue pulling items from the queue until the last item pulled from the // queue is greater than the redner time or greater than the currently targeted item. if (!InterpolateState.Target.HasValue || - (potentialItem.TimeSent <= renderTime && InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) + ((potentialItem.TimeSent <= renderTime) && InterpolateState.Target.Value.TimeSent <= potentialItem.TimeSent)) { if (m_Buffer.TryDequeue(out BufferedItem target)) { @@ -243,6 +257,7 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m alreadyHasBufferItem = true; InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.TimeToTargetValue = minDeltaTime; startTime = InterpolateState.Target.Value.TimeSent; } else @@ -256,17 +271,10 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m } // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated // for each item as opposed to losing the resolution of the values. - if (isSmoothed) - { - InterpolateState.TimeToTargetValue = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); - } - else - { - InterpolateState.TimeToTargetValue = (float)(target.TimeSent - startTime); - } + InterpolateState.TimeToTargetValue = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); InterpolateState.Target = target; } - InterpolateState.DeltaTime = 0.0f; + InterpolateState.ResetDelta(); } } else @@ -284,17 +292,12 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m } /// - /// Used for internal testing - /// - internal T UpdateInternal(float deltaTime, NetworkTime serverTime) - { - return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); - } - - /// - /// ** Recommended to use when is enabled. **
- /// Provides a closer/more precise update between the current and target values that uses a smooth dampening approach. + /// Interpolation Update to use when smooth dampening is enabled on a . ///
+ /// + /// Alternate recommended interpolation when when is enabled.
+ /// This can provide a precise interpolation result between the current and target values at the expense of not being as smooth as then doulbe Lerp approach. + ///
/// The last frame time that is either for non-rigidbody motion and when using ridigbody motion. /// The tick latency in relative local time. /// The minimum time delta between the current and target value. @@ -302,7 +305,7 @@ internal T UpdateInternal(float deltaTime, NetworkTime serverTime) /// The newly interpolated value of type 'T' public T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, float maxDeltaTime) { - TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime, true); + TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime); // Only interpolate when there is a start and end point and we have not already reached the end value if (InterpolateState.Target.HasValue) { @@ -312,6 +315,82 @@ public T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, f m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; } + #endregion + + #region Lerp Interpolation + /// + /// TryConsumeFromBuffer: Lerping Version + /// + /// + /// This version of TryConsumeFromBuffer adheres to the original BufferedLinearInterpolator buffer consumption pattern. + /// + /// + /// + private void TryConsumeFromBuffer(double renderTime, double serverTime) + { + if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime)) + { + BufferedItem? previousItem = null; + var alreadyHasBufferItem = false; + while (m_Buffer.TryPeek(out BufferedItem potentialItem)) + { + // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing + // to consume. + if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) + { + break; + } + + if ((potentialItem.TimeSent <= serverTime) && + (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) + { + if (m_Buffer.TryDequeue(out BufferedItem target)) + { + if (!InterpolateState.Target.HasValue) + { + InterpolateState.Target = target; + + alreadyHasBufferItem = true; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.StartTime = target.TimeSent; + InterpolateState.EndTime = target.TimeSent; + } + else + { + if (!alreadyHasBufferItem) + { + alreadyHasBufferItem = true; + InterpolateState.StartTime = InterpolateState.Target.Value.TimeSent; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + } + InterpolateState.EndTime = target.TimeSent; + InterpolateState.Target = target; + } + InterpolateState.ResetDelta(); + } + } + else + { + break; + } + + if (!InterpolateState.Target.HasValue) + { + break; + } + previousItem = potentialItem; + } + } + } + #endregion + + /// + /// Used for internal testing + /// + internal T UpdateInternal(float deltaTime, NetworkTime serverTime) + { + return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); + } /// /// Call to update the state of the interpolators using Lerp. @@ -325,12 +404,30 @@ public T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, f /// The newly interpolated value of type 'T' public T Update(float deltaTime, double renderTime, double serverTime) { - TryConsumeFromBuffer(renderTime, deltaTime, (float)(serverTime - renderTime)); + TryConsumeFromBuffer(renderTime, serverTime); // Only interpolate when there is a start and end point and we have not already reached the end value if (InterpolateState.Target.HasValue) { - InterpolateState.AddDeltaTime(deltaTime); - var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, InterpolateState.LerpT); + // The original BufferedLinearInterpolator lerping script to assure the Smooth Dampening updates do not impact + // this specific behavior. + float t = 1.0f; + double range = InterpolateState.EndTime - InterpolateState.StartTime; + if (range > k_SmallValue) + { + t = (float)((renderTime - InterpolateState.StartTime) / range); + + if (t < 0.0f) + { + t = 0.0f; + } + + if (t > MaxInterpolationBound) // max extrapolation + { + // TODO this causes issues with teleport, investigate + t = 1.0f; + } + } + var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, t); InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, deltaTime / MaximumInterpolationTime); } m_NbItemsReceivedThisFrame = 0; @@ -352,9 +449,9 @@ public void AddMeasurement(T newMeasurement, double sentTime) { if (m_LastBufferedItemReceived.TimeSent < sentTime) { - m_BufferCount++; + InternalReset(newMeasurement, sentTime, IsAngularValue, false); + m_LastMeasurementAddedTime = sentTime; m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); - ResetTo(newMeasurement, sentTime); // Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues m_Buffer.Enqueue(m_LastBufferedItemReceived); } @@ -362,19 +459,13 @@ public void AddMeasurement(T newMeasurement, double sentTime) } // Drop measurements that are received out of order/late - if (sentTime > m_LastMeasurementAddedTime) + if (sentTime > m_LastMeasurementAddedTime || m_BufferCount == 0) { m_BufferCount++; m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); m_Buffer.Enqueue(m_LastBufferedItemReceived); m_LastMeasurementAddedTime = sentTime; } -#if UNITY_EDITOR - else if (EnableLogging) - { - Debug.Log($"[{m_Name}] Dropping measurement -- Time: {sentTime} Value: {newMeasurement} | Last measurement -- Time: {m_LastMeasurementAddedTime} Value: {m_LastBufferedItemReceived.Item}"); - } -#endif } /// @@ -459,156 +550,5 @@ internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) InterpolateState.Target = end; InLocalSpace = inLocalSpace; } - - // TODO: Collect data points so a single buffered linear interpolator can provide additional data points - // to be visualized in RNSM. - #region DEBUG_LOGGING -#if UNITY_EDITOR - - private string m_Name; - internal bool EnableLogging = false; - internal ulong NetworkObjectId; - internal ushort NetworkBehaviourId; - - private double m_LastDebugUpdate = 0.0; - - private double m_AvgTimeDelta = 0.0; - - private float m_LowestLerpT = float.MaxValue; - private float m_LerpTAverage = 0.0f; - private int m_MaxBufferedItems = 0; - private float m_AverageBufferCount = 0.0f; - - private void LogInfo(double serverTime) - { - if (!EnableLogging) - { - return; - } - if (m_LowestLerpT > InterpolateState.LerpT) - { - m_LowestLerpT = InterpolateState.LerpT; - } - - if (m_LerpTAverage == 0.0f) - { - m_LerpTAverage = InterpolateState.LerpT; - } - else - { - m_LerpTAverage = (m_LerpTAverage + InterpolateState.LerpT) * 0.5f; - } - - if (m_MaxBufferedItems < m_Buffer.Count) - { - m_MaxBufferedItems = m_Buffer.Count; - } - - if (m_AverageBufferCount == 0.0f) - { - m_AverageBufferCount = (m_AverageBufferCount + m_Buffer.Count) * 0.5f; - } - - if (m_AvgTimeDelta == 0.0) - { - m_AvgTimeDelta = InterpolateState.DeltaTime; - } - else - { - m_AvgTimeDelta += InterpolateState.DeltaTime; - m_AvgTimeDelta *= 0.5; - } - - if (m_LastDebugUpdate < serverTime) - { - //Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}][{InterpolateState.Target.Value.ItemId}] Min LerpT: {m_LowestLerpT} | Avg LerpT: {m_LerpTAverage} | Max Count: {m_MaxBufferedItems} | Avg Count: {m_AverageBufferCount} | Avg TD: {m_AvgTimeDelta}"); - Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}] Min LerpT: {m_LowestLerpT} | Avg LerpT: {m_LerpTAverage} | Max Count: {m_MaxBufferedItems} | Avg Count: {m_AverageBufferCount} | Avg TD: {m_AvgTimeDelta}"); - m_LastDebugUpdate = serverTime + 1.0; - m_AverageBufferCount = 0.0f; - m_MaxBufferedItems = 0; - m_LerpTAverage = 0.0f; - m_LowestLerpT = float.MaxValue; - } - } - //private bool m_LogSegment; - - private float m_AvgDepth; - - private float m_AvgRTT; - - private float m_MaxLerpT = 0.0f; - - private void LogSecondInfo(double serverTime, float lerpT, int depth, float serverHalfRtt) - { - if (!EnableLogging) - { - return; - } - if (m_LowestLerpT > lerpT) - { - m_LowestLerpT = lerpT; - } - - if (m_MaxLerpT < lerpT) - { - m_MaxLerpT = lerpT; - } - - if (m_LerpTAverage == 0.0f) - { - m_LerpTAverage = lerpT; - } - else - { - m_LerpTAverage = (m_LerpTAverage + lerpT) * 0.5f; - } - - if (m_AvgRTT == 0.0f) - { - m_AvgRTT = serverHalfRtt; - } - else - { - m_AvgRTT = (m_AvgRTT + serverHalfRtt) * 0.5f; - } - - if (m_AvgDepth == 0.0f) - { - m_AvgDepth = depth; - } - else - { - m_AvgDepth = (m_AvgDepth + depth) * 0.5f; - } - - - - if (m_MaxBufferedItems < m_Buffer.Count) - { - m_MaxBufferedItems = m_Buffer.Count; - } - - if (m_AverageBufferCount == 0.0f) - { - m_AverageBufferCount = (m_AverageBufferCount + m_Buffer.Count) * 0.5f; - } - - if (m_LastDebugUpdate < serverTime) - { - //Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}][{InterpolateState.Target.Value.ItemId}] Min LerpT: {m_LowestLerpT} | Avg LerpT: {m_LerpTAverage} | Max Count: {m_MaxBufferedItems} | Avg Count: {m_AverageBufferCount} | Avg TD: {m_AvgTimeDelta}"); - Debug.Log($"[{m_Name}][{NetworkObjectId}-{NetworkBehaviourId}] Min/Max LT: {m_LowestLerpT}/{m_MaxLerpT} | Avg LT: {m_LerpTAverage} | Max Count: {m_MaxBufferedItems} | Avg Count: {m_AverageBufferCount} | Avg Depth: {m_AvgDepth} Avg HRtt: {m_AvgRTT}"); - m_LastDebugUpdate = serverTime + 1.0; - m_AverageBufferCount = 0.0f; - m_MaxBufferedItems = 0; - m_LerpTAverage = 0.0f; - m_LowestLerpT = float.MaxValue; - m_AvgDepth = 0.0f; - m_AvgRTT = 0.0f; - m_MaxLerpT = 0.0f; - //m_LogSegment = true; - } - } -#endif - #endregion } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 7e4f22b56f..55e7ccf2b7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -2986,10 +2986,11 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf /// (i.e. Position, Scale, and Rotation) /// /// Maximum time boundary that can be used in a frame when interpolating between two values - [Obsolete("This method is no longer valid.", false)] public void SetMaxInterpolationBound(float maxInterpolationBound) { - // No longer valid + m_RotationInterpolator.MaxInterpolationBound = maxInterpolationBound; + m_PositionInterpolator.MaxInterpolationBound = maxInterpolationBound; + m_ScaleInterpolator.MaxInterpolationBound = maxInterpolationBound; } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index 0f8f87c414..0318947093 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -95,6 +95,7 @@ public class NetworkTimeSystem private NetworkTransport m_NetworkTransport; private NetworkTickSystem m_NetworkTickSystem; private NetworkManager m_NetworkManager; + private double m_TickFrequency; /// /// @@ -127,6 +128,7 @@ internal NetworkTickSystem Initialize(NetworkManager networkManager) m_NetworkTransport = networkManager.NetworkConfig.NetworkTransport; m_TimeSyncFrequencyTicks = (int)(k_TimeSyncFrequency * networkManager.NetworkConfig.TickRate); m_NetworkTickSystem = new NetworkTickSystem(networkManager.NetworkConfig.TickRate, 0, 0); + m_TickFrequency = 1.0 / networkManager.NetworkConfig.TickRate; // Only the server side needs to register for tick based time synchronization if (m_ConnectionManager.LocalClient.IsServer) { @@ -222,7 +224,7 @@ public bool Advance(double deltaTimeSec) // TODO: For client-server, we need a latency message sent by clients to tell us their tick latency if (LastSyncedRttSec > 0.0f) { - m_TickLatencyAverage = Mathf.Lerp(m_TickLatencyAverage, (float)((LastSyncedRttSec + deltaTimeSec) / m_NetworkTickSystem.ServerTime.FixedDeltaTimeAsDouble), (float)deltaTimeSec); + m_TickLatencyAverage = Mathf.Lerp(m_TickLatencyAverage, (float)((LastSyncedRttSec + deltaTimeSec) / m_TickFrequency), (float)deltaTimeSec); TickLatency = (int)Mathf.Max(1.0f, Mathf.Round(m_TickLatencyAverage)); } From ccd50268a071eeddbf17951933190f5af185a9cd Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 5 Mar 2025 15:34:23 -0600 Subject: [PATCH 18/41] test Made an adjustment to the InterpolatorTests.TestUpdatingInterpolatorWithNoData,. Also fixed the two tests that were disabled due to no extrapolation. --- .../Tests/Editor/InterpolatorTests.cs | 64 ++----------------- .../Runtime/TransformInterpolationTests.cs | 1 + 2 files changed, 5 insertions(+), 60 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs index 906f681e11..666183abe6 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs @@ -1,4 +1,3 @@ -using System; using NUnit.Framework; namespace Unity.Netcode.EditorTests @@ -105,7 +104,6 @@ public void OutOfOrderShouldStillWork() Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2f).Within(k_Precision)); } - [Ignore("TODO: Fix this test to still handle testing message loss without extrapolation. This is tracked in MTT-11338")] [Test] public void MessageLoss() { @@ -144,45 +142,7 @@ public void MessageLoss() serverTime += timeStep; // t = 3 interpolator.UpdateInternal((float)timeStep, serverTime); Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2f)); - - // extrapolating to 2.5 - serverTime += timeStep; // t = 3.5d - interpolator.UpdateInternal((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(2.5f)); - - // next value skips to where it was supposed to be once buffer time is showing the next value - serverTime += timeStep; // t = 4 - interpolator.UpdateInternal((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(3f)); - - // interpolation continues as expected - serverTime += timeStep; // t = 4.5 - interpolator.UpdateInternal((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(3.5f)); - - serverTime += timeStep; // t = 5 - interpolator.UpdateInternal((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(4f)); - - // lost time=6, extrapolating - serverTime += timeStep; // t = 5.5 - interpolator.UpdateInternal((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(4.5f)); - - serverTime += timeStep; // t = 6.0 - interpolator.UpdateInternal((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(5f)); - - // misprediction - serverTime += timeStep; // t = 6.5 - interpolator.UpdateInternal((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(5.5f)); - - // lerp to right value - serverTime += timeStep; // t = 7.0 - interpolator.UpdateInternal((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.GreaterThan(6.0f)); - Assert.That(interpolator.GetInterpolatedValue(), Is.LessThanOrEqualTo(100f)); + // Since there is no extrapolation, the rest of this test was removed. } [Test] @@ -302,11 +262,10 @@ public void TestUpdatingInterpolatorWithNoData() { var interpolator = new BufferedLinearInterpolatorFloat(); var serverTime = new NetworkTime(k_MockTickRate, 0.0d); - // invalid case, this is undefined behaviour - Assert.Throws(() => interpolator.UpdateInternal(1f, serverTime)); + var interpolatedValue = interpolator.UpdateInternal(1f, serverTime); + Assert.IsTrue(interpolatedValue == 0.0f, $"Expected the result to be 0.0f but was {interpolatedValue}!"); } - [Ignore("TODO: Fix this test to still test duplicated values without extrapolation. This is tracked in MTT-11338")] [Test] public void TestDuplicatedValues() { @@ -340,22 +299,7 @@ public void TestDuplicatedValues() serverTime += 0.5d; // t = 3 interp = interpolator.UpdateInternal(0.5f, serverTime); Assert.That(interp, Is.EqualTo(2f)); - - // with unclamped interpolation, we continue mispredicting since the two last values are actually treated as the same. Therefore we're not stopping at "2" - serverTime += 0.5d; // t = 3.5 - interp = interpolator.UpdateInternal(0.5f, serverTime); - Assert.That(interp, Is.EqualTo(2.5f)); - - serverTime += 0.5d; // t = 4 - interp = interpolator.UpdateInternal(0.5f, serverTime); - Assert.That(interp, Is.EqualTo(3f)); - - // we add a measurement with an updated time - var pastServerTime = new NetworkTime(k_MockTickRate, 3.0d); - interpolator.AddMeasurement(2f, pastServerTime.Time); - - interp = interpolator.UpdateInternal(0.5f, serverTime); - Assert.That(interp, Is.EqualTo(2f)); + // Since there is no extrapolation, the rest of this test was removed. } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 982f3f4116..4e0cea8e53 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -43,6 +43,7 @@ protected override void OnInitialize(ref NetworkTransformState replicatedState) m_LocalSpaceToggles = 0; m_FrameRateFractional = 1.0f / Application.targetFrameRate; PositionThreshold = MinThreshold; + SetMaxInterpolationBound(1.0f); base.OnInitialize(ref replicatedState); } From f3d564e735a5f470f498bcd14f22ddcbe9c2ad3c Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 5 Mar 2025 15:45:30 -0600 Subject: [PATCH 19/41] fix Minor fix for an issue when dividing up the smooth damp and lerp buffer consumption. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 0121665a9d..5aceefdf29 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -225,9 +225,8 @@ private void InternalReset(T targetValue, double serverTime, bool isAngularValue /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime) { - var canGetNextItem = InterpolateState.TargetTimeAproximatelyReached() || IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item); - - if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime && canGetNextItem)) + if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime && + (InterpolateState.TargetTimeAproximatelyReached() || IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item)))) { BufferedItem? previousItem = null; var startTime = 0.0; From 877f4ccb9c6551b4cb2fe025d2ce255b1c1df7e1 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 5 Mar 2025 20:18:02 -0600 Subject: [PATCH 20/41] update Made some more adjustments as well as added a predicted smooth dampened value to help with smoothing and to get closer to the target value on the non-authority side. Updated the Quaternion's Approximation to use the dot product instead. --- .../BufferedLinearInterpolator.cs | 40 +++++++++++++------ .../BufferedLinearInterpolatorQuaternion.cs | 5 +-- .../Runtime/Components/NetworkTransform.cs | 12 +++--- .../Runtime/Timing/NetworkTimeSystem.cs | 2 +- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 5aceefdf29..a0b76a5a7f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -71,6 +71,7 @@ public void AddDeltaTime(float deltaTime) else { m_AverageDeltaTime += deltaTime; + m_AverageDeltaTime *= 0.5f; } DeltaTime = Math.Min(DeltaTime + m_AverageDeltaTime, TimeToTargetValue); LerpT = TimeToTargetValue == 0.0f ? 1.0f : DeltaTime / TimeToTargetValue; @@ -97,7 +98,7 @@ public void Reset(T currentValue) CurrentValue = currentValue; PreviousValue = currentValue; // When reset, we consider ourselves to have already arrived at the target (even if no target is set) - LerpT = 1.0f; + LerpT = 0.0f; EndTime = 0.0; StartTime = 0.0; ResetDelta(); @@ -169,6 +170,11 @@ public void Reset(T currentValue) /// private T m_RateOfChange; + /// + /// Represents the predicted rate of change for the value being interpolated when smooth dampening is enabled. + /// + private T m_PredictedRateOfChange; + private bool m_IsAngularValue; /// @@ -225,8 +231,8 @@ private void InternalReset(T targetValue, double serverTime, bool isAngularValue /// maximum time delta which defines the maximum time duration when consuming more than one item from the buffer private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float maxDeltaTime) { - if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime && - (InterpolateState.TargetTimeAproximatelyReached() || IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item)))) + if (!InterpolateState.Target.HasValue || (InterpolateState.Target.Value.TimeSent <= renderTime + && (InterpolateState.TargetTimeAproximatelyReached() || IsAproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item)))) { BufferedItem? previousItem = null; var startTime = 0.0; @@ -309,7 +315,15 @@ public T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, f if (InterpolateState.Target.HasValue) { InterpolateState.AddDeltaTime(deltaTime); - InterpolateState.CurrentValue = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, deltaTime); + + // Smooth dampen our current time + var current = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime); + // Smooth dampen a predicted time based on our average delta time + var predict = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); + // Split the difference between the two. + // Note: Since smooth dampening cannot over shoot, both current and predict will eventually become the same or will be very close to the same. + // Upon stopping motion, the final resing value should be a very close aproximation of the authority side. + InterpolateState.CurrentValue = Interpolate(current, predict, 0.5f); } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; @@ -381,15 +395,6 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) } } } - #endregion - - /// - /// Used for internal testing - /// - internal T UpdateInternal(float deltaTime, NetworkTime serverTime) - { - return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); - } /// /// Call to update the state of the interpolators using Lerp. @@ -432,6 +437,15 @@ public T Update(float deltaTime, double renderTime, double serverTime) m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; } + #endregion + + /// + /// Used for internal testing + /// + internal T UpdateInternal(float deltaTime, NetworkTime serverTime) + { + return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); + } /// /// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value". diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs index 532f7dc286..8e75dec162 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs @@ -63,10 +63,7 @@ protected internal override Quaternion SmoothDamp(Quaternion current, Quaternion /// protected internal override bool IsAproximately(Quaternion first, Quaternion second, float precision) { - return Mathf.Abs(first.x - second.x) <= precision && - Mathf.Abs(first.y - second.y) <= precision && - Mathf.Abs(first.z - second.z) <= precision && - Mathf.Abs(first.w - second.w) <= precision; + return (1.0f - Quaternion.Dot(first, second)) <= precision; } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 55e7ccf2b7..565324c20a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1820,11 +1820,13 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, ref Tra var positionThreshold = Vector3.one * PositionThreshold; var rotationThreshold = Vector3.one * RotAngleThreshold; - if (m_UseRigidbodyForMotion) - { - positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold(); - rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold(); - } + // NSS: Disabling this for the time being + // TODO: Determine if we actually need this and if not remove this from NetworkRigidBodyBase + //if (m_UseRigidbodyForMotion) + //{ + // positionThreshold = m_NetworkRigidbodyInternal.GetAdjustedPositionThreshold(); + // rotationThreshold = m_NetworkRigidbodyInternal.GetAdjustedRotationThreshold(); + //} #else var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position; var rotation = InLocalSpace ? transformToUse.localRotation : transformToUse.rotation; diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index 0318947093..c3fc9e8b67 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -225,7 +225,7 @@ public bool Advance(double deltaTimeSec) if (LastSyncedRttSec > 0.0f) { m_TickLatencyAverage = Mathf.Lerp(m_TickLatencyAverage, (float)((LastSyncedRttSec + deltaTimeSec) / m_TickFrequency), (float)deltaTimeSec); - TickLatency = (int)Mathf.Max(1.0f, Mathf.Round(m_TickLatencyAverage)); + TickLatency = (int)Mathf.Max(2.0f, Mathf.Round(m_TickLatencyAverage)); } if (Math.Abs(m_DesiredLocalTimeOffset - m_CurrentLocalTimeOffset) > HardResetThresholdSec || Math.Abs(m_DesiredServerTimeOffset - m_CurrentServerTimeOffset) > HardResetThresholdSec) From 69480f07d29ef4004fbdcf28594b3bf771bd4718 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 5 Mar 2025 20:19:12 -0600 Subject: [PATCH 21/41] test Fixing a bug with one of the tests (evidently was not caught using the previous BLI) --- .../Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 3a7d0be8c7..467880fd10 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -268,7 +268,7 @@ public void TestMultipleExplicitSetStates([Values] Interpolation interpolation) [Test] public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolation) { - var interpolate = interpolation != Interpolation.EnableInterpolate; + var interpolate = interpolation == Interpolation.EnableInterpolate; m_AuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 0.1f; From d7f14dc0cabbf08ea65918a4607913611cb60488 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 5 Mar 2025 21:31:57 -0600 Subject: [PATCH 22/41] update Some fixes for the one test that was failing and more TestFixture additions. --- .../Editor/NetworkTransformEditor.cs | 24 ++++++++++-- .../Runtime/Components/NetworkTransform.cs | 38 +++++++++++++------ .../NetworkTransformGeneral.cs | 35 ++++++++++++++--- .../NetworkTransform/NetworkTransformTests.cs | 7 ++++ 4 files changed, 84 insertions(+), 20 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index 3c1182e3e3..d6b8232204 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -28,7 +28,9 @@ public class NetworkTransformEditor : NetcodeEditorBase private SerializedProperty m_ScaleThresholdProperty; private SerializedProperty m_InLocalSpaceProperty; private SerializedProperty m_InterpolateProperty; - private SerializedProperty m_InterpolationTypeProperty; + private SerializedProperty m_PositionInterpolationTypeProperty; + private SerializedProperty m_RotationInterpolationTypeProperty; + private SerializedProperty m_ScaleInterpolationTypeProperty; private SerializedProperty m_UseQuaternionSynchronization; private SerializedProperty m_UseQuaternionCompression; @@ -62,7 +64,12 @@ public override void OnEnable() m_ScaleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleThreshold)); m_InLocalSpaceProperty = serializedObject.FindProperty(nameof(NetworkTransform.InLocalSpace)); m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate)); - m_InterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.InterpolationType)); + + m_PositionInterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.PositionInterpolationType)); + m_RotationInterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.RotationInterpolationType)); + m_ScaleInterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleInterpolationType)); + + m_UseQuaternionSynchronization = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionSynchronization)); m_UseQuaternionCompression = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionCompression)); m_UseHalfFloatPrecision = serializedObject.FindProperty(nameof(NetworkTransform.UseHalfFloatPrecision)); @@ -163,7 +170,18 @@ private void DisplayNetworkTransformProperties() EditorGUILayout.PropertyField(m_InterpolateProperty); if (networkTransform.Interpolate) { - EditorGUILayout.PropertyField(m_InterpolationTypeProperty); + if (networkTransform.SynchronizePosition) + { + EditorGUILayout.PropertyField(m_PositionInterpolationTypeProperty); + } + if (networkTransform.SynchronizeRotation) + { + EditorGUILayout.PropertyField(m_RotationInterpolationTypeProperty); + } + if (networkTransform.SynchronizeScale) + { + EditorGUILayout.PropertyField(m_ScaleInterpolationTypeProperty); + } } } EditorGUILayout.PropertyField(m_SlerpPosition); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 565324c20a..d5dbb0e85c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -952,12 +952,28 @@ public enum InterpolationTypes } /// - /// The interpolation type to use for the instance. + /// The position interpolation type to use for the instance. /// /// /// For more details review . /// - public InterpolationTypes InterpolationType; + public InterpolationTypes PositionInterpolationType; + + /// + /// The rotation interpolation type to use for the instance. + /// + /// + /// For more details review . + /// + public InterpolationTypes RotationInterpolationType; + + /// + /// The scale interpolation type to use for the instance. + /// + /// + /// For more details review . + /// + public InterpolationTypes ScaleInterpolationType; /// /// When is enabled and using , this adjust the maximum interpolation time for position. @@ -1084,7 +1100,7 @@ public enum AuthorityModes /// public bool SyncPositionZ = true; - private bool SynchronizePosition + internal bool SynchronizePosition { get { @@ -1119,7 +1135,7 @@ private bool SynchronizePosition /// public bool SyncRotAngleZ = true; - private bool SynchronizeRotation + internal bool SynchronizeRotation { get { @@ -1151,7 +1167,7 @@ private bool SynchronizeRotation /// public bool SyncScaleZ = true; - private bool SynchronizeScale + internal bool SynchronizeScale { get { @@ -3187,7 +3203,9 @@ protected virtual void Awake() { if (AssignDefaultInterpolationType) { - InterpolationType = DefaultInterpolationType; + PositionInterpolationType = DefaultInterpolationType; + RotationInterpolationType = DefaultInterpolationType; + ScaleInterpolationType = DefaultInterpolationType; } // Rotation is a single Quaternion since each Euler axis will affect the quaternion's final value m_RotationInterpolator = new BufferedLinearInterpolatorQuaternion(); @@ -3771,12 +3789,10 @@ private void UpdateInterpolation() var minDeltaTime = m_CachedNetworkManager.LocalTime.FixedDeltaTime; var maxDeltaTime = (tickLatency * m_CachedNetworkManager.ServerTime.FixedDeltaTime); - var useLerp = InterpolationType == InterpolationTypes.Lerp; - // Now only update the interpolators for the portions of the transform being synchronized if (SynchronizePosition) { - if (useLerp) + if (PositionInterpolationType == InterpolationTypes.Lerp) { m_PositionInterpolator.MaximumInterpolationTime = PositionMaxInterpolationTime; m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); @@ -3789,7 +3805,7 @@ private void UpdateInterpolation() if (SynchronizeRotation) { - if (useLerp) + if (RotationInterpolationType == InterpolationTypes.Lerp) { m_RotationInterpolator.MaximumInterpolationTime = RotationMaxInterpolationTime; // When using half precision Lerp towards the target rotation. @@ -3806,7 +3822,7 @@ private void UpdateInterpolation() if (SynchronizeScale) { - if (useLerp) + if (ScaleInterpolationType == InterpolationTypes.Lerp) { m_ScaleInterpolator.MaximumInterpolationTime = ScaleMaxInterpolationTime; m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 467880fd10..14a12b2b32 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -4,18 +4,36 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(HostOrServer.Host, Authority.OwnerAuthority)] - [TestFixture(HostOrServer.Host, Authority.ServerAuthority)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(HostOrServer.Host, Authority.OwnerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] + [TestFixture(HostOrServer.Host, Authority.ServerAuthority, NetworkTransform.InterpolationTypes.SmoothDampening)] internal class NetworkTransformGeneral : NetworkTransformBase { - public NetworkTransformGeneral(HostOrServer testWithHost, Authority authority) : + public NetworkTransformGeneral(HostOrServer testWithHost, Authority authority, NetworkTransform.InterpolationTypes interpolationType) : base(testWithHost, authority, RotationCompression.None, Rotation.Euler, Precision.Full) - { } + { + NetworkTransform.AssignDefaultInterpolationType = true; + NetworkTransform.DefaultInterpolationType = interpolationType; + } protected override bool m_EnableTimeTravel => true; protected override bool m_SetupIsACoroutine => false; protected override bool m_TearDownIsACoroutine => false; + protected override void OnInlineTearDown() + { + m_EnableVerboseDebug = false; + base.OnInlineTearDown(); + } + + protected override void OnOneTimeTearDown() + { + NetworkTransform.AssignDefaultInterpolationType = false; + NetworkTransform.DefaultInterpolationType = NetworkTransform.InterpolationTypes.Lerp; + base.OnOneTimeTearDown(); + } + /// /// Test to verify nonAuthority cannot change the transform directly /// @@ -268,21 +286,26 @@ public void TestMultipleExplicitSetStates([Values] Interpolation interpolation) [Test] public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolation) { + m_EnableVerboseDebug = true; var interpolate = interpolation == Interpolation.EnableInterpolate; m_AuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 0.1f; + // Give a little bit of time before sending things with this test + for (int i = 0; i < 10; i++) + { + TimeTravelAdvanceTick(); + } // Test one parameter at a time first var newPosition = new Vector3(125f, 35f, 65f); var newRotation = Quaternion.Euler(1, 2, 3); var newScale = new Vector3(2.0f, 2.0f, 2.0f); m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); - var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition)); + var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 120); Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); - m_NonAuthoritativeTransform.SetState(null, newRotation, null, interpolate); success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles)); Assert.True(success, $"Timed out waiting for non-authoritative rotation state request to be applied!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 6716b980d9..6418e0f23f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -71,6 +71,13 @@ public NetworkTransformTests(HostOrServer testWithHost, Authority authority, Rot NetworkTransform.DefaultInterpolationType = interpolation; } + protected override void OnOneTimeTearDown() + { + NetworkTransform.AssignDefaultInterpolationType = false; + NetworkTransform.DefaultInterpolationType = NetworkTransform.InterpolationTypes.Lerp; + base.OnOneTimeTearDown(); + } + protected override bool m_EnableTimeTravel => true; protected override bool m_SetupIsACoroutine => false; protected override bool m_TearDownIsACoroutine => false; From 7d021349b0eada31fbd7a7aa64744fe75ba82d2e Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 5 Mar 2025 21:49:04 -0600 Subject: [PATCH 23/41] test Not sure why these two SetState lerp interpolation tests are having issues as it passes in the editor but fails at runtime... most likely a timing related issue. --- .../Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 14a12b2b32..9690545188 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -302,7 +302,7 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati var newRotation = Quaternion.Euler(1, 2, 3); var newScale = new Vector3(2.0f, 2.0f, 2.0f); m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); - var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 120); + var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 800); Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); From fc9ee50d7316c96865d4ca62c30b7d7e65a4d800 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 5 Mar 2025 22:00:51 -0600 Subject: [PATCH 24/41] test and update Clearing out the 2nd rate of change. Adding delay for the end of the last test in NetworkTransformGeneral. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 2 ++ .../Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index a0b76a5a7f..af9d86432f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -192,6 +192,7 @@ public void Clear() m_LastMeasurementAddedTime = 0.0; InterpolateState.Reset(default); m_RateOfChange = default; + m_PredictedRateOfChange = default; } /// @@ -211,6 +212,7 @@ public void ResetTo(T targetValue, double serverTime, bool isAngularValue = fals private void InternalReset(T targetValue, double serverTime, bool isAngularValue = false, bool addMeasurement = true) { m_RateOfChange = default; + m_PredictedRateOfChange = default; // Set our initial value InterpolateState.Reset(targetValue); m_IsAngularValue = isAngularValue; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 9690545188..11b1e25ac9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -302,7 +302,7 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati var newRotation = Quaternion.Euler(1, 2, 3); var newScale = new Vector3(2.0f, 2.0f, 2.0f); m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); - var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 800); + var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 120); Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); @@ -324,7 +324,7 @@ public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolati newScale = new Vector3(0.5f, 0.5f, 0.5f); m_NonAuthoritativeTransform.SetState(newPosition, newRotation, newScale, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), 120); Assert.True(success, $"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); From a5d9bf8048c716d4bc53a6d0a913959a38c51401 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 02:45:51 -0600 Subject: [PATCH 25/41] Test Seeing if this fixes the issue. --- .../NetworkTransformGeneral.cs | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 11b1e25ac9..efb1ce1244 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -21,14 +21,9 @@ public NetworkTransformGeneral(HostOrServer testWithHost, Authority authority, N protected override bool m_SetupIsACoroutine => false; protected override bool m_TearDownIsACoroutine => false; - protected override void OnInlineTearDown() - { - m_EnableVerboseDebug = false; - base.OnInlineTearDown(); - } - protected override void OnOneTimeTearDown() { + m_EnableVerboseDebug = false; NetworkTransform.AssignDefaultInterpolationType = false; NetworkTransform.DefaultInterpolationType = NetworkTransform.InterpolationTypes.Lerp; base.OnOneTimeTearDown(); @@ -286,45 +281,45 @@ public void TestMultipleExplicitSetStates([Values] Interpolation interpolation) [Test] public void NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolation) { - m_EnableVerboseDebug = true; var interpolate = interpolation == Interpolation.EnableInterpolate; m_AuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.Interpolate = interpolate; m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 0.1f; - // Give a little bit of time before sending things with this test - for (int i = 0; i < 10; i++) - { - TimeTravelAdvanceTick(); - } + + m_EnableVerboseDebug = true; + + m_AuthoritativeTransform.Teleport(Vector3.zero, Quaternion.identity, Vector3.one); + var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(Vector3.zero, Quaternion.identity.eulerAngles, Vector3.one), 800); + Assert.True(success, $"Timed out waiting for initialization to be applied!"); // Test one parameter at a time first - var newPosition = new Vector3(125f, 35f, 65f); + var newPosition = new Vector3(55f, 35f, 65f); var newRotation = Quaternion.Euler(1, 2, 3); var newScale = new Vector3(2.0f, 2.0f, 2.0f); m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate); - var success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 120); + success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatchesValue(newPosition), 800); Assert.True(success, $"Timed out waiting for non-authoritative position state request to be applied!"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); m_NonAuthoritativeTransform.SetState(null, newRotation, null, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => RotationMatchesValue(newRotation.eulerAngles), 800); Assert.True(success, $"Timed out waiting for non-authoritative rotation state request to be applied!"); Assert.True(Approximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), "Authoritative rotation does not match!"); Assert.True(Approximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), "Non-Authoritative rotation does not match!"); m_NonAuthoritativeTransform.SetState(null, null, newScale, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale)); + success = WaitForConditionOrTimeOutWithTimeTravel(() => ScaleMatchesValue(newScale), 800); Assert.True(success, $"Timed out waiting for non-authoritative scale state request to be applied!"); Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), "Authoritative scale does not match!"); Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), "Non-Authoritative scale does not match!"); // Test all parameters at once - newPosition = new Vector3(55f, 95f, -25f); + newPosition = new Vector3(-10f, 95f, -25f); newRotation = Quaternion.Euler(20, 5, 322); newScale = new Vector3(0.5f, 0.5f, 0.5f); m_NonAuthoritativeTransform.SetState(newPosition, newRotation, newScale, interpolate); - success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), 120); + success = WaitForConditionOrTimeOutWithTimeTravel(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale), 800); Assert.True(success, $"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!"); Assert.True(Approximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!"); Assert.True(Approximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!"); From e5dc3653eecdbc87a14bd27b0467922521ee48e9 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 03:58:24 -0600 Subject: [PATCH 26/41] update Minor adjustment to smooth dampening. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 7 ++++--- .../Runtime/Components/NetworkTransform.cs | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index af9d86432f..e1b659e4ca 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -279,6 +279,7 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m // TODO: We might consider creating yet another queue to add these items to and assure that the time is accelerated // for each item as opposed to losing the resolution of the values. InterpolateState.TimeToTargetValue = Mathf.Clamp((float)(target.TimeSent - startTime), minDeltaTime, maxDeltaTime); + InterpolateState.Target = target; } InterpolateState.ResetDelta(); @@ -321,11 +322,11 @@ public T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, f // Smooth dampen our current time var current = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_RateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime); // Smooth dampen a predicted time based on our average delta time - var predict = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + InterpolateState.AverageDeltaTime); - // Split the difference between the two. + var predict = SmoothDamp(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, ref m_PredictedRateOfChange, InterpolateState.TimeToTargetValue, InterpolateState.DeltaTime + (InterpolateState.AverageDeltaTime * 2)); + // Lerp between the current and predicted. // Note: Since smooth dampening cannot over shoot, both current and predict will eventually become the same or will be very close to the same. // Upon stopping motion, the final resing value should be a very close aproximation of the authority side. - InterpolateState.CurrentValue = Interpolate(current, predict, 0.5f); + InterpolateState.CurrentValue = Interpolate(current, predict, deltaTime); } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index d5dbb0e85c..7c7b4270b6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3786,8 +3786,10 @@ private void UpdateInterpolation() } var tickLatencyAsTime = m_CachedNetworkManager.LocalTime.TimeTicksAgo(tickLatency).Time; + // Smooth dampening specific: + // We clamp between tick rate and bit beyond the tick rate but not 2x tick rate (we predict 2x out) var minDeltaTime = m_CachedNetworkManager.LocalTime.FixedDeltaTime; - var maxDeltaTime = (tickLatency * m_CachedNetworkManager.ServerTime.FixedDeltaTime); + var maxDeltaTime = (1.666667f * m_CachedNetworkManager.ServerTime.FixedDeltaTime); // Now only update the interpolators for the portions of the transform being synchronized if (SynchronizePosition) From d9175309531edebcabe4288434810d6a02fc6e4a Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 10:27:58 -0600 Subject: [PATCH 27/41] update adding xml api documentation, tooltips, and some editor adjustments. --- .../Editor/NetworkTransformEditor.cs | 46 +++++++++++-- .../BufferedLinearInterpolator.cs | 14 +++- .../Runtime/Components/NetworkTransform.cs | 66 ++++++++++++++++--- 3 files changed, 111 insertions(+), 15 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index d6b8232204..7dc66b9c01 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -32,6 +32,10 @@ public class NetworkTransformEditor : NetcodeEditorBase private SerializedProperty m_RotationInterpolationTypeProperty; private SerializedProperty m_ScaleInterpolationTypeProperty; + private SerializedProperty m_PositionMaximumInterpolationTimeProperty; + private SerializedProperty m_RotationMaximumInterpolationTimeProperty; + private SerializedProperty m_ScaleMaximumInterpolationTimeProperty; + private SerializedProperty m_UseQuaternionSynchronization; private SerializedProperty m_UseQuaternionCompression; private SerializedProperty m_UseHalfFloatPrecision; @@ -66,8 +70,11 @@ public override void OnEnable() m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate)); m_PositionInterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.PositionInterpolationType)); + m_PositionMaximumInterpolationTimeProperty = serializedObject.FindProperty(nameof(NetworkTransform.PositionMaxInterpolationTime)); m_RotationInterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.RotationInterpolationType)); + m_RotationMaximumInterpolationTimeProperty = serializedObject.FindProperty(nameof(NetworkTransform.RotationMaxInterpolationTime)); m_ScaleInterpolationTypeProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleInterpolationType)); + m_ScaleMaximumInterpolationTimeProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleMaxInterpolationTime)); m_UseQuaternionSynchronization = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionSynchronization)); @@ -150,9 +157,21 @@ private void DisplayNetworkTransformProperties() } EditorGUILayout.Space(); EditorGUILayout.LabelField("Thresholds", EditorStyles.boldLabel); - EditorGUILayout.PropertyField(m_PositionThresholdProperty); - EditorGUILayout.PropertyField(m_RotAngleThresholdProperty); - EditorGUILayout.PropertyField(m_ScaleThresholdProperty); + if (networkTransform.SynchronizePosition) + { + EditorGUILayout.PropertyField(m_PositionThresholdProperty); + } + + if (networkTransform.SynchronizeRotation) + { + EditorGUILayout.PropertyField(m_RotAngleThresholdProperty); + } + + if (networkTransform.SynchronizeScale) + { + EditorGUILayout.PropertyField(m_ScaleThresholdProperty); + } + EditorGUILayout.Space(); EditorGUILayout.LabelField("Delivery", EditorStyles.boldLabel); EditorGUILayout.PropertyField(m_TickSyncChildren); @@ -173,18 +192,37 @@ private void DisplayNetworkTransformProperties() if (networkTransform.SynchronizePosition) { EditorGUILayout.PropertyField(m_PositionInterpolationTypeProperty); + // Only display when using Lerp. + if (networkTransform.PositionInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + { + EditorGUILayout.PropertyField(m_SlerpPosition); + EditorGUILayout.PropertyField(m_PositionMaximumInterpolationTimeProperty); + } } if (networkTransform.SynchronizeRotation) { EditorGUILayout.PropertyField(m_RotationInterpolationTypeProperty); + + // Only display when using Lerp. + if (networkTransform.RotationInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + { + EditorGUILayout.PropertyField(m_RotationMaximumInterpolationTimeProperty); + } } if (networkTransform.SynchronizeScale) { EditorGUILayout.PropertyField(m_ScaleInterpolationTypeProperty); + // Only display when using Lerp. + if (networkTransform.ScaleInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + { + EditorGUILayout.PropertyField(m_ScaleMaximumInterpolationTimeProperty); + } } } + } - EditorGUILayout.PropertyField(m_SlerpPosition); + + EditorGUILayout.PropertyField(m_UseQuaternionSynchronization); if (m_UseQuaternionSynchronization.boolValue) { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index e1b659e4ca..4abe8b7793 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -134,10 +134,15 @@ public void Reset(T currentValue) private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator - /// - /// There's two factors affecting interpolation: buffering (set in NetworkManager's NetworkTimeSystem) and interpolation time, which is the amount of time it'll take to reach the target. This is to affect the second one. + /// Determines how much smoothing will be applied to the 2nd lerp when using the (i.e. lerping and not smooth dampening). /// + /// + /// There's two factors affecting interpolation:
+ /// - Buffering: Which can be adjusted in set in the .
+ /// - Interpolation time: The divisor applied to delta time where the quotient is used as the lerp time. + ///
+ [Range(0.016f, 1.0f)] public float MaximumInterpolationTime = 0.1f; /// @@ -435,7 +440,10 @@ public T Update(float deltaTime, double renderTime, double serverTime) } } var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, t); - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, deltaTime / MaximumInterpolationTime); + + // Assure our MaximumInterpolationTime is valid and that the second lerp time ranges between deltaTime and 1.0f. + var secondLerpTime = Mathf.Clamp(deltaTime / Mathf.Max(deltaTime, MaximumInterpolationTime), deltaTime, 1.0f); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, target, secondLerpTime); } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 7c7b4270b6..869e0ef5e1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -937,17 +937,26 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #region PROPERTIES AND GENERAL METHODS /// - /// The two ways to smooth during interpolation. + /// The different interpolation types used with to help smooth interpolation results. /// public enum InterpolationTypes { /// /// Uses lerping and yields a linear progression between two values. /// + /// + /// For more information:
+ /// -
+ /// -
+ /// -
+ ///
Lerp, /// - /// Uses a smooth dampening approach and is recommended when is enabled. + /// Uses a smooth dampening approach for interpolating between two data points and adjusts based on rate of change. /// + /// + /// Unlike , there are no additional values needed to be adjusted for this interpolation type. + /// SmoothDampening } @@ -955,37 +964,78 @@ public enum InterpolationTypes /// The position interpolation type to use for the instance. ///
/// - /// For more details review . + /// - yields a traditional linear result.
+ /// - adjusts based on the rate of change.
+ /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
+ /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
///
+ [Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes PositionInterpolationType; /// /// The rotation interpolation type to use for the instance. /// /// - /// For more details review . + /// - yields a traditional linear result.
+ /// - adjusts based on the rate of change.
+ /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
+ /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
///
+ [Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes RotationInterpolationType; /// /// The scale interpolation type to use for the instance. /// /// - /// For more details review . + /// - yields a traditional linear result.
+ /// - adjusts based on the rate of change.
+ /// - You can have mixed interpolation types between position, rotation, and scale on the same instance.
+ /// - You can change the interpolation type during runtime, but changing between can result in a slight stutter if the object is in motion.
///
+ [Tooltip("Lerping yields a traditional linear result where smooth dampening will adjust based on the rate of change. You can mix interpolation types for position, rotation, and scale.")] public InterpolationTypes ScaleInterpolationType; /// - /// When is enabled and using , this adjust the maximum interpolation time for position. + /// The position interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. + /// - The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct).
+ /// - The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
+ /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). ///
+ /// + /// - Only used When is enabled and using .
+ /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + ///
+ [Tooltip("The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct). The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] + [Range(0.01f, 1.0f)] public float PositionMaxInterpolationTime = 0.1f; + /// - /// When is enabled and using , this adjust the maximum interpolation time for rotation. + /// The rotation interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. + /// - The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct).
+ /// - The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
+ /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). ///
+ /// + /// - Only used When is enabled and using .
+ /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + ///
+ [Tooltip("The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct). The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] + [Range(0.01f, 1.0f)] public float RotationMaxInterpolationTime = 0.1f; + /// - /// When is enabled and using , this adjust the maximum interpolation time for scale. + /// The scale interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. + /// - The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct).
+ /// - The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
+ /// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). ///
+ /// + /// - Only used When is enabled and using .
+ /// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) + ///
+ [Tooltip("The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct). The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] + [Range(0.01f, 1.0f)] public float ScaleMaxInterpolationTime = 0.1f; public enum AuthorityModes From 91eca5f58b8933af8da21a49beee3991268ef06e Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 10:58:54 -0600 Subject: [PATCH 28/41] style removing white space --- .../Editor/NetworkTransformEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index 7dc66b9c01..620fd88244 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -221,8 +221,8 @@ private void DisplayNetworkTransformProperties() } } - - + + EditorGUILayout.PropertyField(m_UseQuaternionSynchronization); if (m_UseQuaternionSynchronization.boolValue) { From 639fbf8462c3b05db47f90cb302cf1d08d06f076 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 11:54:35 -0600 Subject: [PATCH 29/41] update - pvp Adding updates to resolve PVP issues for NetworkTransform. --- .../Runtime/Components/NetworkTransform.cs | 49 ++++++++++++++----- pvpExceptions.json | 15 ------ 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 869e0ef5e1..d52b441e96 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -420,6 +420,7 @@ internal set /// UseUnreliableDeltas is enabled. When set, the entire transform will /// be or has been synchronized. ///
+ /// true or false as to whether this state update was an unreliable frame synchronization. public bool IsUnreliableFrameSync() { return UnreliableFrameSync; @@ -432,6 +433,7 @@ public bool IsUnreliableFrameSync() /// /// Unreliable delivery will only be used if is set. /// + /// true or false as to whether this state update was sent with reliable delivery. public bool IsReliableStateUpdate() { return ReliableSequenced; @@ -615,9 +617,7 @@ public int GetNetworkTick() internal HalfVector3 HalfEulerRotation; - /// - /// Serializes this - /// + /// public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { // Used to calculate the LastSerializedSize value @@ -1038,11 +1038,25 @@ public enum InterpolationTypes [Range(0.01f, 1.0f)] public float ScaleMaxInterpolationTime = 0.1f; + /// + /// Determines if the server or client owner pushes transform states. + /// public enum AuthorityModes { + /// + /// Server pushes transform state updates + /// Server, + /// + /// Client owner pushes transform state updates. + /// Owner, } + + /// + /// Determines whether this instance will have state updates pushed by the server or the client owner. + /// + /// #if MULTIPLAYER_SERVICES_SDK_INSTALLED [Tooltip("Selects who has authority (sends state updates) over the transform. When the network topology is set to distributed authority, this always defaults to owner authority. If server (the default), then only server-side adjustments to the " + "transform will be synchronized with clients. If owner (or client), then only the owner-side adjustments to the transform will be synchronized with both the server and other clients.")] @@ -1052,7 +1066,6 @@ public enum AuthorityModes #endif public AuthorityModes AuthorityMode; - /// /// When enabled, any parented s (children) of this will be forced to synchronize their transform when this instance sends a state update.
/// This can help to reduce out of sync updates that can lead to slight jitter between a parent and its child/children. @@ -1312,7 +1325,14 @@ internal bool SynchronizeScale /// public bool SwitchTransformSpaceWhenParented = false; + /// + /// Returns true if position is currently in local space and false if it is in world space. + /// protected bool PositionInLocalSpace => (!SwitchTransformSpaceWhenParented && InLocalSpace) || (m_PositionInterpolator != null && m_PositionInterpolator.InLocalSpace && SwitchTransformSpaceWhenParented); + + /// + /// Returns true if rotation is currently in local space and false if it is in world space. + /// protected bool RotationInLocalSpace => (!SwitchTransformSpaceWhenParented && InLocalSpace) || (m_RotationInterpolator != null && m_RotationInterpolator.InLocalSpace && SwitchTransformSpaceWhenParented); /// @@ -1575,16 +1595,12 @@ private bool ShouldSynchronizeHalfFloat(ulong targetClientId) #region ONSYNCHRONIZE - /// - /// This is invoked when a new client joins (server and client sides) - /// Server Side: Serializes as if we were teleporting (everything is sent via NetworkTransformState) - /// Client Side: Adds the interpolated state which applies the NetworkTransformState as well - /// + /// /// - /// If a derived class overrides this, then make sure to invoke this base method! + /// This is invoked when a new client joins (server and client sides). + /// Server Side: Serializes as if we were teleporting (everything is sent via NetworkTransformState). + /// Client Side: Adds the interpolated state which applies the NetworkTransformState as well. /// - /// The serializer type for buffer operations - /// The buffer serializer used for network state synchronization protected override void OnSynchronize(ref BufferSerializer serializer) { var targetClientId = m_TargetIdBeingSynchronized; @@ -2345,6 +2361,9 @@ internal void UpdatePositionInterpolator(Vector3 position, double time, bool res internal bool LogMotion; + /// + /// Virtual method invoked on the non-authority side after a new state has been received and applied. + /// protected virtual void OnTransformUpdated() { @@ -2958,6 +2977,9 @@ protected virtual void OnNetworkTransformStateUpdated(ref NetworkTransformState } + /// + /// Virtual method that is invoked on the non-authority side when a state update has been recieved but not yet applied. + /// protected virtual void OnBeforeUpdateTransformState() { @@ -3516,6 +3538,7 @@ public override void OnGainedOwnership() base.OnGainedOwnership(); } + /// protected override void OnOwnershipChanged(ulong previous, ulong current) { // If we were the previous owner or the newly assigned owner then reinitialize @@ -4108,6 +4131,7 @@ internal static float GetTickLatency(NetworkManager networkManager) /// /// Only valid on clients. /// + /// Returns the tick latency and local offset in seconds and as a float value. public static float GetTickLatency() { return GetTickLatency(NetworkManager.Singleton); @@ -4125,6 +4149,7 @@ internal static float GetTickLatencyInSeconds(NetworkManager networkManager) /// /// Returns the tick latency in seconds (typically fractional) /// + /// Returns the current tick latency in seconds as a float value. public static float GetTickLatencyInSeconds() { return GetTickLatencyInSeconds(NetworkManager.Singleton); diff --git a/pvpExceptions.json b/pvpExceptions.json index bda7ccbfe0..009e00d436 100644 --- a/pvpExceptions.json +++ b/pvpExceptions.json @@ -45,21 +45,6 @@ "Unity.Netcode.Components.NetworkDeltaPosition: void NetworkSerialize(BufferSerializer): missing ", "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetConvertedDelta(): missing ", "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetDeltaPosition(): missing ", - "Unity.Netcode.Components.NetworkTransform: AuthorityMode: undocumented", - "Unity.Netcode.Components.NetworkTransform: PositionInLocalSpace: undocumented", - "Unity.Netcode.Components.NetworkTransform: RotationInLocalSpace: undocumented", - "Unity.Netcode.Components.NetworkTransform: void OnTransformUpdated(): undocumented", - "Unity.Netcode.Components.NetworkTransform: void OnBeforeUpdateTransformState(): undocumented", - "Unity.Netcode.Components.NetworkTransform: void OnOwnershipChanged(ulong, ulong): undocumented", - "Unity.Netcode.Components.NetworkTransform: float GetTickLatency(): missing ", - "Unity.Netcode.Components.NetworkTransform: float GetTickLatencyInSeconds(): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: bool IsUnreliableFrameSync(): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: bool IsReliableStateUpdate(): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkTransform.NetworkTransformState: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkTransform.AuthorityModes: undocumented", - "Unity.Netcode.Components.NetworkTransform.AuthorityModes: Server: undocumented", - "Unity.Netcode.Components.NetworkTransform.AuthorityModes: Owner: undocumented", "Unity.Netcode.NetworkConfig: Prefabs: undocumented", "Unity.Netcode.NetworkConfig: NetworkTopology: undocumented", "Unity.Netcode.NetworkConfig: UseCMBService: undocumented", From f369d0948eb820d587dcf1079bb290f93cb3393d Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 12:03:23 -0600 Subject: [PATCH 30/41] update - pvp Fixing the remainder of the PVP issues within the components area. --- .../Runtime/Components/HalfVector3.cs | 4 +--- .../Runtime/Components/HalfVector4.cs | 4 +--- .../BufferedLinearInterpolator.cs | 2 +- .../Components/NetworkDeltaPosition.cs | 6 +++--- pvpExceptions.json | 19 ------------------- 5 files changed, 6 insertions(+), 29 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs index ce696ec965..d9367b010a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector3.cs @@ -74,9 +74,7 @@ private void SerializeRead(FastBufferReader reader) } } - /// - /// The serialization implementation of . - /// + /// public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { if (serializer.IsReader) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs index 1ff2b09076..8d7a000f38 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/HalfVector4.cs @@ -58,9 +58,7 @@ private void SerializeRead(FastBufferReader reader) } } - /// - /// The serialization implementation of . - /// + /// public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { if (serializer.IsReader) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 4abe8b7793..d9a180e510 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -32,7 +32,7 @@ protected internal struct BufferedItem public double TimeSent; /// - /// The constructor + /// The constructor. /// /// The item value. /// The time the item was sent. diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs index 2ed982d3e3..1a112991db 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs @@ -27,9 +27,7 @@ public struct NetworkDeltaPosition : INetworkSerializable internal bool CollapsedDeltaIntoBase; - /// - /// The serialization implementation of - /// + /// public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { if (!SynchronizeBase) @@ -105,6 +103,7 @@ public Vector3 GetFullPosition() /// /// Only applies to the authoritative side for instances. /// + /// Returns the half float version of the current delta position. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3 GetConvertedDelta() { @@ -120,6 +119,7 @@ public Vector3 GetConvertedDelta() /// Precision loss adjustments are one network tick behind on the /// non-authoritative side. /// + /// The full precision delta position value as a . [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3 GetDeltaPosition() { diff --git a/pvpExceptions.json b/pvpExceptions.json index 009e00d436..4be5ff1064 100644 --- a/pvpExceptions.json +++ b/pvpExceptions.json @@ -26,25 +26,6 @@ "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Position: undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Rotation: undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform.TransformState: Scale: undocumented", - "Unity.Netcode.Components.HalfVector3: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.HalfVector3: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.HalfVector4: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.HalfVector4: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.BufferedLinearInterpolator: m_InterpStartValue: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: m_CurrentInterpValue: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: m_InterpEndValue: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: m_Buffer: undocumented", - "Unity.Netcode.BufferedLinearInterpolator: void OnConvertTransformSpace(Transform, bool): undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: Item: undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: TimeSent: undocumented", - "Unity.Netcode.BufferedLinearInterpolator.BufferedItem: .ctor(T, double): undocumented", - "Unity.Netcode.BufferedLinearInterpolatorQuaternion: void OnConvertTransformSpace(Transform, bool): undocumented", - "Unity.Netcode.BufferedLinearInterpolatorVector3: void OnConvertTransformSpace(Transform, bool): undocumented", - "Unity.Netcode.Components.NetworkDeltaPosition: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkDeltaPosition: void NetworkSerialize(BufferSerializer): missing ", - "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetConvertedDelta(): missing ", - "Unity.Netcode.Components.NetworkDeltaPosition: Vector3 GetDeltaPosition(): missing ", "Unity.Netcode.NetworkConfig: Prefabs: undocumented", "Unity.Netcode.NetworkConfig: NetworkTopology: undocumented", "Unity.Netcode.NetworkConfig: UseCMBService: undocumented", From be8e1760548f2f411c26bf23976381573e8801a1 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 12:08:46 -0600 Subject: [PATCH 31/41] style Fixing minor text issues with the newly added XML API documentation. --- .../Runtime/Components/NetworkTransform.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index d52b441e96..e227512f7b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -998,43 +998,43 @@ public enum InterpolationTypes /// /// The position interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. - /// - The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct).
- /// - The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
+ /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
+ /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
/// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). ///
/// /// - Only used When is enabled and using .
/// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) ///
- [Tooltip("The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct). The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] + [Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] public float PositionMaxInterpolationTime = 0.1f; /// /// The rotation interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. - /// - The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct).
- /// - The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
+ /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
+ /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
/// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). ///
/// /// - Only used When is enabled and using .
/// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) ///
- [Tooltip("The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct). The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] + [Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] public float RotationMaxInterpolationTime = 0.1f; /// /// The scale interoplation time divisor applied to the current delta time (dividend) where the quotient yields the time used for the second smoothing lerp. - /// - The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct).
- /// - The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
+ /// - The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct).
+ /// - The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).
/// - This value can be adjusted during runtime in the event you want to dynamically adjust it based on some other value (i.e. velocity or the like). ///
/// /// - Only used When is enabled and using .
/// - The quotient will be clamped to a value that ranges from 1.0f to the current delta time (i.e. or ) ///
- [Tooltip("The lower the value the smoother, but can result in lost data points (i.e. quick changes in direct). The higher the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] + [Tooltip("The higher the value the smoother, but can result in lost data points (i.e. quick changes in direct). The lower the value the more accurate/precise, but can result in slight stutter (i.e. due to jitter, latency, or a high threshold value).")] [Range(0.01f, 1.0f)] public float ScaleMaxInterpolationTime = 0.1f; From 7000fae579243c56b6cd0d45e8966fc96537fa3c Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 12:40:52 -0600 Subject: [PATCH 32/41] update Putting back the T Update(float deltaTime, NetworkTime serverTime) method and marking it as being deprectated. --- .../Interpolator/BufferedLinearInterpolator.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index d9a180e510..7d183a0b66 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -450,6 +450,20 @@ public T Update(float deltaTime, double renderTime, double serverTime) } #endregion + /// + /// Convenience version of 'Update' mainly for testing + /// the reason we don't want to always call this version is so that on the calling side we can compute + /// the renderTime once for the many things being interpolated (and the many interpolators per object) + /// + /// time since call + /// current server time + /// The newly interpolated value of type 'T' + [Obsolete("This method is being deprecated due to it being only used for internal testing purposes.", false)] + public T Update(float deltaTime, NetworkTime serverTime) + { + return UpdateInternal(deltaTime, serverTime); + } + /// /// Used for internal testing /// From 0fe85d2a4626a73edf7c3886f873de107d6b1424 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 13:08:05 -0600 Subject: [PATCH 33/41] test Adding a smooth dampening pass to the NestedNetworkTransformTests. --- .../NestedNetworkTransformTests.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs index 0cae4ecb12..60cbf38082 100644 --- a/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs @@ -34,6 +34,19 @@ namespace TestProject.RuntimeTests [TestFixture(Interpolation.NoInterpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] [TestFixture(Interpolation.NoInterpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + // Smooth dampening interpolation pass + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.TickSynchronized)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Full, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Half, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Server, NestedTickSynchronization.NormalSynchronize)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening, Interpolation.Interpolation, Precision.Compressed, NetworkTransform.AuthorityModes.Owner, NestedTickSynchronization.NormalSynchronize)] public class NestedNetworkTransformTests : IntegrationTestWithApproximation { private const string k_TestScene = "NestedNetworkTransformTestScene"; @@ -55,6 +68,7 @@ public class NestedNetworkTransformTests : IntegrationTestWithApproximation private Precision m_Precision; private NetworkTransform.AuthorityModes m_Authority; private NestedTickSynchronization m_NestedTickSynchronization; + private NetworkTransform.InterpolationTypes m_InterpolationType; public enum Interpolation { @@ -82,14 +96,19 @@ public enum NestedTickSynchronization } - public NestedNetworkTransformTests(Interpolation interpolation, Precision precision, NetworkTransform.AuthorityModes authoritativeModel, NestedTickSynchronization nestedTickSynchronization) + public NestedNetworkTransformTests(NetworkTransform.InterpolationTypes interpolationType, Interpolation interpolation, Precision precision, NetworkTransform.AuthorityModes authoritativeModel, NestedTickSynchronization nestedTickSynchronization) { + m_InterpolationType = interpolationType; m_Interpolation = interpolation; m_Precision = precision; m_Authority = authoritativeModel; m_NestedTickSynchronization = nestedTickSynchronization; } + public NestedNetworkTransformTests(Interpolation interpolation, Precision precision, NetworkTransform.AuthorityModes authoritativeModel, NestedTickSynchronization nestedTickSynchronization) : + this(NetworkTransform.InterpolationTypes.Lerp, interpolation, precision, authoritativeModel, nestedTickSynchronization) + { } + public NestedNetworkTransformTests() { @@ -130,6 +149,8 @@ protected override void OnOneTimeTearDown() protected override IEnumerator OnSetup() { + NetworkTransform.AssignDefaultInterpolationType = true; + NetworkTransform.DefaultInterpolationType = m_InterpolationType; yield return WaitForConditionOrTimeOut(() => m_BaseSceneLoaded.IsValid() && m_BaseSceneLoaded.isLoaded); AssertOnTimeout($"Timed out waiting for scene {k_TestScene} to load!"); } @@ -148,6 +169,8 @@ protected override void OnInlineTearDown() { // This prevents us from trying to destroy the resource loaded m_PlayerPrefab = null; + NetworkTransform.AssignDefaultInterpolationType = false; + NetworkTransform.DefaultInterpolationType = NetworkTransform.InterpolationTypes.Lerp; } private void ConfigureNetworkTransform(IntegrationNetworkTransform networkTransform) From e453cdf662dfff1a6104cd03f1afd6f73ab472e7 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 13:11:40 -0600 Subject: [PATCH 34/41] update adding change log entries --- com.unity.netcode.gameobjects/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index d3417214a5..49983d5c47 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,11 +10,15 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added interpolator types as an inspector view selection for position, rotation, and scale. (#3337) +- Added a new smooth dampening interpolator type that provides a nice balance between precision and smoothing results. (#3337) +- Added `NetworkTimeSystem.TickLatency` property that provides the average latency of a client. (#3337) - Added `FastBufferReader(ArraySegment buffer, Allocator copyAllocator)` constructor that uses the `ArraySegment.Offset` as the `FastBufferReader` offset and the `ArraySegment.Count` as the `FastBufferReader` length. (#3321) - Added `FastBufferReader(ArraySegment buffer, Allocator copyAllocator, int length = -1)` constructor that uses the `ArraySegment.Offset` as the `FastBufferReader` offset. (#3321) ### Fixed +- Fixed issue where the `MaximumInterpolationTime` could not be modified from within the inspector view or runtime. (#3337) - Fixed issue where `AnticipatedNetworkVariable` previous value returned by `AnticipatedNetworkVariable.OnAuthoritativeValueChanged` is updated correctly on the non-authoritative side. (#3306) - Fixed `OnClientConnectedCallback` passing incorrect `clientId` when scene management is disabled. (#3312) - Fixed issue where the `NetworkObject.Ownership` custom editor did not take the default "Everything" flag into consideration. (#3305) @@ -30,6 +34,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Changed `BufferedLinearInterpolator.Update(float deltaTime, NetworkTime serverTime)` as being deprecated since this method is only used for internal testing purposes. (#3337) - Changed root in-scene placed `NetworkObject` instances now will always have either the `Distributable` permission set unless the `SessionOwner` permission is set. (#3305) - Changed the `DestroyObject` message to reduce the serialized message size and remove the unnecessary message field. (#3304) - Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212) From bed3a335925f971cc51a54033f4b89f9dd017ab5 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 18:02:20 -0600 Subject: [PATCH 35/41] style Adding additional comment on the max delta time calculation. --- .../Runtime/Components/NetworkTransform.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index e227512f7b..d0951c6cfb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3862,6 +3862,10 @@ private void UpdateInterpolation() // Smooth dampening specific: // We clamp between tick rate and bit beyond the tick rate but not 2x tick rate (we predict 2x out) var minDeltaTime = m_CachedNetworkManager.LocalTime.FixedDeltaTime; + // The 1.666667f value is a "magic" number tht lies between the FixedDeltaTime and 2 * the averaged + // frame update. Since smooth dampening is most useful for Rigidbody motion, the physics update + // frequency is roughly 60hz (59.x?) which 2x that value as frequency is typically close to 32-33ms. + // Look within the Interpolator.Update for smooth dampening to better understand the above. var maxDeltaTime = (1.666667f * m_CachedNetworkManager.ServerTime.FixedDeltaTime); // Now only update the interpolators for the portions of the transform being synchronized From 354b036d0220ea6cd81a109a11c76d3e911eb635 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 18:07:33 -0600 Subject: [PATCH 36/41] update Fixing some xml api text issues. Making sure we are clearing the interpolator when reaching the maximum boundary. --- .../Components/Interpolator/BufferedLinearInterpolator.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 7d183a0b66..031151818e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -201,7 +201,7 @@ public void Clear() } /// - /// Resets the current interpolator to the target valueTeleports current interpolation value to targetValue. + /// Resets the current interpolator to the target value. /// /// /// This is used when first synchronizing/initializing and when telporting an object. @@ -308,7 +308,7 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m /// Interpolation Update to use when smooth dampening is enabled on a . ///
/// - /// Alternate recommended interpolation when when is enabled.
+ /// Alternate recommended interpolation when is enabled.
/// This can provide a precise interpolation result between the current and target values at the expense of not being as smooth as then doulbe Lerp approach. ///
/// The last frame time that is either for non-rigidbody motion and when using ridigbody motion. @@ -487,6 +487,9 @@ public void AddMeasurement(T newMeasurement, double sentTime) { if (m_LastBufferedItemReceived.TimeSent < sentTime) { + // Clear the interpolator + Clear(); + // Reset to the new value InternalReset(newMeasurement, sentTime, IsAngularValue, false); m_LastMeasurementAddedTime = sentTime; m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); From 15a6f9f9338f59b98eb33693e0f52da1e1b09427 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 6 Mar 2025 19:23:27 -0600 Subject: [PATCH 37/41] update Adding indentions to the new NetworkTransform inspector view properties grouping. --- .../Editor/NetworkTransformEditor.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index 620fd88244..9acf4b61c0 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -43,6 +43,7 @@ public class NetworkTransformEditor : NetcodeEditorBase private SerializedProperty m_AuthorityMode; private static int s_ToggleOffset = 45; + private static int s_IndentOffset = 15; private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5; private static GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position"); private static GUIContent s_RotationLabel = EditorGUIUtility.TrTextContent("Rotation"); @@ -191,38 +192,35 @@ private void DisplayNetworkTransformProperties() { if (networkTransform.SynchronizePosition) { - EditorGUILayout.PropertyField(m_PositionInterpolationTypeProperty); + DrawIndentedPropertyField(1, m_PositionInterpolationTypeProperty); // Only display when using Lerp. if (networkTransform.PositionInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { - EditorGUILayout.PropertyField(m_SlerpPosition); - EditorGUILayout.PropertyField(m_PositionMaximumInterpolationTimeProperty); + DrawIndentedPropertyField(2, m_SlerpPosition); + DrawIndentedPropertyField(2, m_PositionMaximumInterpolationTimeProperty); } } if (networkTransform.SynchronizeRotation) { - EditorGUILayout.PropertyField(m_RotationInterpolationTypeProperty); - + DrawIndentedPropertyField(1, m_RotationInterpolationTypeProperty); // Only display when using Lerp. if (networkTransform.RotationInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { - EditorGUILayout.PropertyField(m_RotationMaximumInterpolationTimeProperty); + DrawIndentedPropertyField(2, m_RotationMaximumInterpolationTimeProperty); } } if (networkTransform.SynchronizeScale) { - EditorGUILayout.PropertyField(m_ScaleInterpolationTypeProperty); + DrawIndentedPropertyField(1, m_ScaleInterpolationTypeProperty); // Only display when using Lerp. if (networkTransform.ScaleInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { - EditorGUILayout.PropertyField(m_ScaleMaximumInterpolationTimeProperty); + DrawIndentedPropertyField(2, m_ScaleMaximumInterpolationTimeProperty); } } } - } - EditorGUILayout.PropertyField(m_UseQuaternionSynchronization); if (m_UseQuaternionSynchronization.boolValue) { @@ -252,7 +250,16 @@ private void DisplayNetworkTransformProperties() #endif // COM_UNITY_MODULES_PHYSICS2D } - + private void DrawIndentedPropertyField(int indentLevel, SerializedProperty property) + { + var originalWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = originalWidth - (indentLevel * s_IndentOffset) - 2; + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.Space(indentLevel * s_IndentOffset, false); + EditorGUILayout.PropertyField(property, GUILayout.ExpandWidth(true)); + EditorGUILayout.EndHorizontal(); + EditorGUIUtility.labelWidth = originalWidth; + } /// public override void OnInspectorGUI() From a51365f05042a15b4ae9fe8b41519fea4136ecc8 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 7 Mar 2025 07:40:07 -0600 Subject: [PATCH 38/41] update Moving the property field draw method into the NetcodeEditorBase class in case we wanted to expose it at a later date and/or want to use it for another I-view control. --- .../Editor/NetcodeEditorBase.cs | 19 +++++++++++++ .../Editor/NetworkTransformEditor.cs | 27 ++++++------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs index 4b8a351a35..2da18bffca 100644 --- a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs +++ b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs @@ -11,11 +11,30 @@ namespace Unity.Netcode.Editor [CanEditMultipleObjects] public partial class NetcodeEditorBase : UnityEditor.Editor where TT : MonoBehaviour { + private const int k_IndentOffset = 15; + /// public virtual void OnEnable() { } + /// + /// Will draw a property field with an indention level using the default or a specified offset per indention. + /// + /// The serialized property to draw as a default field + /// The indention level. + /// Optional indention level offset. + protected internal void DrawIndentedPropertyField(SerializedProperty property, int indentLevel, int offset = k_IndentOffset) + { + var originalWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = originalWidth - (indentLevel * offset); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.Space(indentLevel * offset, false); + EditorGUILayout.PropertyField(property, GUILayout.ExpandWidth(true)); + EditorGUILayout.EndHorizontal(); + EditorGUIUtility.labelWidth = originalWidth; + } + /// /// Helper method to draw the properties of the specified child type component within a FoldoutHeaderGroup. /// diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index 9acf4b61c0..5cdc0ccb98 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -43,7 +43,7 @@ public class NetworkTransformEditor : NetcodeEditorBase private SerializedProperty m_AuthorityMode; private static int s_ToggleOffset = 45; - private static int s_IndentOffset = 15; + private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5; private static GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position"); private static GUIContent s_RotationLabel = EditorGUIUtility.TrTextContent("Rotation"); @@ -192,30 +192,30 @@ private void DisplayNetworkTransformProperties() { if (networkTransform.SynchronizePosition) { - DrawIndentedPropertyField(1, m_PositionInterpolationTypeProperty); + DrawIndentedPropertyField(m_PositionInterpolationTypeProperty, 1); // Only display when using Lerp. if (networkTransform.PositionInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { - DrawIndentedPropertyField(2, m_SlerpPosition); - DrawIndentedPropertyField(2, m_PositionMaximumInterpolationTimeProperty); + DrawIndentedPropertyField(m_SlerpPosition, 2); + DrawIndentedPropertyField(m_PositionMaximumInterpolationTimeProperty, 2); } } if (networkTransform.SynchronizeRotation) { - DrawIndentedPropertyField(1, m_RotationInterpolationTypeProperty); + DrawIndentedPropertyField(m_RotationInterpolationTypeProperty, 1); // Only display when using Lerp. if (networkTransform.RotationInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { - DrawIndentedPropertyField(2, m_RotationMaximumInterpolationTimeProperty); + DrawIndentedPropertyField(m_RotationMaximumInterpolationTimeProperty, 2); } } if (networkTransform.SynchronizeScale) { - DrawIndentedPropertyField(1, m_ScaleInterpolationTypeProperty); + DrawIndentedPropertyField(m_ScaleInterpolationTypeProperty, 1); // Only display when using Lerp. if (networkTransform.ScaleInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { - DrawIndentedPropertyField(2, m_ScaleMaximumInterpolationTimeProperty); + DrawIndentedPropertyField(m_ScaleMaximumInterpolationTimeProperty, 2); } } } @@ -250,17 +250,6 @@ private void DisplayNetworkTransformProperties() #endif // COM_UNITY_MODULES_PHYSICS2D } - private void DrawIndentedPropertyField(int indentLevel, SerializedProperty property) - { - var originalWidth = EditorGUIUtility.labelWidth; - EditorGUIUtility.labelWidth = originalWidth - (indentLevel * s_IndentOffset) - 2; - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.Space(indentLevel * s_IndentOffset, false); - EditorGUILayout.PropertyField(property, GUILayout.ExpandWidth(true)); - EditorGUILayout.EndHorizontal(); - EditorGUIUtility.labelWidth = originalWidth; - } - /// public override void OnInspectorGUI() { From ee3797762f16dc9b00a3a281d1b6f0e0d9603033 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 7 Mar 2025 12:45:23 -0600 Subject: [PATCH 39/41] update Adding final touches to the UI updates for the NetworkTransformEditor. Also did a PVP pass for all of the editor related classes. --- .../NetcodeForGameObjectsProjectSettings.cs | 13 +++ .../Configuration/NetworkPrefabProcessor.cs | 3 + .../Configuration/NetworkPrefabsEditor.cs | 4 + .../Editor/NetcodeEditorBase.cs | 86 ++++++++++++++++--- .../Editor/NetworkTransformEditor.cs | 29 +++++-- pvpExceptions.json | 8 -- 6 files changed, 116 insertions(+), 27 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs index e5d18b6bb8..52bd0ef011 100644 --- a/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs @@ -3,11 +3,21 @@ namespace Unity.Netcode.Editor.Configuration { + /// + /// A of type . + /// [FilePath("ProjectSettings/NetcodeForGameObjects.asset", FilePathAttribute.Location.ProjectFolder)] public class NetcodeForGameObjectsProjectSettings : ScriptableSingleton { internal static readonly string DefaultNetworkPrefabsPath = "Assets/DefaultNetworkPrefabs.asset"; + /// + /// The path and name for the DefaultNetworkPrefabs asset. + /// [SerializeField] public string NetworkPrefabsPath = DefaultNetworkPrefabsPath; + + /// + /// A temporary network prefabs path used internally. + /// public string TempNetworkPrefabsPath; private void OnEnable() @@ -19,6 +29,9 @@ private void OnEnable() TempNetworkPrefabsPath = NetworkPrefabsPath; } + /// + /// Used to determine whether the default network prefabs asset should be generated or not. + /// [SerializeField] public bool GenerateDefaultNetworkPrefabs = true; diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs index 55f5fcbfc4..f74530e60f 100644 --- a/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabProcessor.cs @@ -9,6 +9,9 @@ namespace Unity.Netcode.Editor.Configuration ///
public class NetworkPrefabProcessor : AssetPostprocessor { + /// + /// The path to the default network prefabs list. + /// public static string DefaultNetworkPrefabsPath { get diff --git a/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabsEditor.cs b/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabsEditor.cs index 0845068be7..68113ca32f 100644 --- a/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabsEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/Configuration/NetworkPrefabsEditor.cs @@ -4,6 +4,9 @@ namespace Unity.Netcode.Editor { + /// + /// The custom editor for the . + /// [CustomEditor(typeof(NetworkPrefabsList), true)] [CanEditMultipleObjects] public class NetworkPrefabsEditor : UnityEditor.Editor @@ -82,6 +85,7 @@ private void OnEnable() m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs"); } + /// public override void OnInspectorGUI() { using (new EditorGUI.DisabledScope(true)) diff --git a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs index 2da18bffca..1d5f944047 100644 --- a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs +++ b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs @@ -8,10 +8,18 @@ namespace Unity.Netcode.Editor /// The base Netcode Editor helper class to display derived based components
/// where each child generation's properties will be displayed within a FoldoutHeaderGroup. ///
+ /// + /// Defines the base derived component type where 's type T + /// refers to any child derived class of . This provides the ability to have multiple child generation derived custom + /// editos that each child derivation handles drawing its unique properies from that of its parent class. + /// + /// The base derived component type [CanEditMultipleObjects] public partial class NetcodeEditorBase : UnityEditor.Editor where TT : MonoBehaviour { private const int k_IndentOffset = 15; + protected int IndentOffset { get; private set; } = 0; + protected int IndentLevel { get; private set; } = 0; /// public virtual void OnEnable() @@ -19,26 +27,82 @@ public virtual void OnEnable() } /// - /// Will draw a property field with an indention level using the default or a specified offset per indention. + /// Draws a with the option to provide the font style to use. /// - /// The serialized property to draw as a default field - /// The indention level. - /// Optional indention level offset. - protected internal void DrawIndentedPropertyField(SerializedProperty property, int indentLevel, int offset = k_IndentOffset) + /// The serialized property () to draw within the inspector view. + /// The font style () to use when drawing the label of the property field. + protected void DrawPropertyField(SerializedProperty property, FontStyle fontStyle = FontStyle.Normal) + { + var originalLabelFontStyle = EditorStyles.label.fontStyle; + EditorStyles.label.fontStyle = fontStyle; + EditorGUILayout.PropertyField(property); + EditorStyles.label.fontStyle = originalLabelFontStyle; + } + + /// + /// Draws an indented with the option to provide the font style to use. + /// + /// + /// For additional information:
+ /// -
+ /// -
+ ///
+ /// The serialized property () to draw within the inspector view. + /// The font style () to use when drawing the label of the property field. + protected void DrawIndentedPropertyField(SerializedProperty property, FontStyle fontStyle = FontStyle.Normal) { var originalWidth = EditorGUIUtility.labelWidth; - EditorGUIUtility.labelWidth = originalWidth - (indentLevel * offset); - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.Space(indentLevel * offset, false); - EditorGUILayout.PropertyField(property, GUILayout.ExpandWidth(true)); - EditorGUILayout.EndHorizontal(); + EditorGUIUtility.labelWidth = originalWidth - (IndentOffset * IndentLevel); + DrawPropertyField(property, fontStyle); EditorGUIUtility.labelWidth = originalWidth; } + /// + /// Will begin a new indention level for drawing propery fields. + /// + /// + /// You *must* call when returning back to the previous indention level.
+ /// For additional information:
+ /// -
+ /// -
+ ///
+ /// (optional) The size in pixels of the offset. If no value passed, then it uses a default of 15 pixels. + protected void BeginIndent(int offset = k_IndentOffset) + { + IndentOffset = offset; + IndentLevel++; + GUILayout.BeginHorizontal(); + GUILayout.Space(IndentOffset); + GUILayout.BeginVertical(); + } + + /// + /// Will end the current indention level when drawing propery fields. + /// + /// + /// For additional information:
+ /// -
+ /// -
+ ///
+ protected void EndIndent() + { + if (IndentLevel == 0) + { + Debug.LogWarning($"Invoking {nameof(EndIndent)} when the indent level is already 0!"); + return; + } + IndentLevel--; + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + } + /// /// Helper method to draw the properties of the specified child type component within a FoldoutHeaderGroup. /// - /// The specific child type that should have its properties drawn. + /// + /// must be a sub-class of the root parent class type . + /// + /// The specific child derived type of or the type of that should have its properties drawn. /// The component type of the . /// The to invoke that will draw the type properties. /// The current expanded property value diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index 5cdc0ccb98..4370d5ca56 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -187,37 +187,50 @@ private void DisplayNetworkTransformProperties() EditorGUILayout.PropertyField(m_InLocalSpaceProperty); if (!networkTransform.HideInterpolateValue) { - EditorGUILayout.PropertyField(m_InterpolateProperty); if (networkTransform.Interpolate) { + EditorGUILayout.Space(); + } + DrawPropertyField(m_InterpolateProperty, networkTransform.Interpolate ? FontStyle.Bold : FontStyle.Normal); + if (networkTransform.Interpolate) + { + BeginIndent(); if (networkTransform.SynchronizePosition) { - DrawIndentedPropertyField(m_PositionInterpolationTypeProperty, 1); + DrawIndentedPropertyField(m_PositionInterpolationTypeProperty); // Only display when using Lerp. if (networkTransform.PositionInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { - DrawIndentedPropertyField(m_SlerpPosition, 2); - DrawIndentedPropertyField(m_PositionMaximumInterpolationTimeProperty, 2); + BeginIndent(); + DrawIndentedPropertyField(m_SlerpPosition); + DrawIndentedPropertyField(m_PositionMaximumInterpolationTimeProperty); + EndIndent(); } } if (networkTransform.SynchronizeRotation) { - DrawIndentedPropertyField(m_RotationInterpolationTypeProperty, 1); + DrawIndentedPropertyField(m_RotationInterpolationTypeProperty); // Only display when using Lerp. if (networkTransform.RotationInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { - DrawIndentedPropertyField(m_RotationMaximumInterpolationTimeProperty, 2); + BeginIndent(); + DrawIndentedPropertyField(m_RotationMaximumInterpolationTimeProperty); + EndIndent(); } } if (networkTransform.SynchronizeScale) { - DrawIndentedPropertyField(m_ScaleInterpolationTypeProperty, 1); + DrawIndentedPropertyField(m_ScaleInterpolationTypeProperty); // Only display when using Lerp. if (networkTransform.ScaleInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { - DrawIndentedPropertyField(m_ScaleMaximumInterpolationTimeProperty, 2); + BeginIndent(); + DrawIndentedPropertyField(m_ScaleMaximumInterpolationTimeProperty); + EndIndent(); } } + EndIndent(); + EditorGUILayout.Space(); } } diff --git a/pvpExceptions.json b/pvpExceptions.json index 4be5ff1064..e717d6626f 100644 --- a/pvpExceptions.json +++ b/pvpExceptions.json @@ -7,14 +7,6 @@ }, "PVP-151-1": { "errors": [ - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: undocumented", - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: NetworkPrefabsPath: undocumented", - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: TempNetworkPrefabsPath: undocumented", - "Unity.Netcode.Editor.Configuration.NetcodeForGameObjectsProjectSettings: GenerateDefaultNetworkPrefabs: undocumented", - "Unity.Netcode.Editor.Configuration.NetworkPrefabProcessor: DefaultNetworkPrefabsPath: undocumented", - "Unity.Netcode.Editor.NetworkPrefabsEditor: undocumented", - "Unity.Netcode.Editor.NetworkPrefabsEditor: void OnInspectorGUI(): undocumented", - "Unity.Netcode.Editor.NetcodeEditorBase: missing ", "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnUpdate(): undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkSpawn(): undocumented", "Unity.Netcode.Components.AnticipatedNetworkTransform: void OnNetworkDespawn(): undocumented", From 08dc79a260f254d64185ab3259273eb6870470d0 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 7 Mar 2025 17:25:35 -0600 Subject: [PATCH 40/41] update Fixing some access permissions and sorting through what we are ready to release as a public API. --- .../Editor/NetcodeEditorBase.cs | 46 ++++++-------- .../Editor/NetworkTransformEditor.cs | 14 ++--- .../BufferedLinearInterpolator.cs | 61 ++++++++++++++----- .../BufferedLinearInterpolatorFloat.cs | 4 +- .../BufferedLinearInterpolatorQuaternion.cs | 4 +- .../BufferedLinearInterpolatorVector3.cs | 4 +- 6 files changed, 75 insertions(+), 58 deletions(-) diff --git a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs index 1d5f944047..b61a77e6b6 100644 --- a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs +++ b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs @@ -18,8 +18,9 @@ namespace Unity.Netcode.Editor public partial class NetcodeEditorBase : UnityEditor.Editor where TT : MonoBehaviour { private const int k_IndentOffset = 15; - protected int IndentOffset { get; private set; } = 0; - protected int IndentLevel { get; private set; } = 0; + private int m_IndentOffset = 0; + private int m_IndentLevel = 0; + private float m_OriginalLabelWidth; /// public virtual void OnEnable() @@ -31,29 +32,14 @@ public virtual void OnEnable() ///
/// The serialized property () to draw within the inspector view. /// The font style () to use when drawing the label of the property field. - protected void DrawPropertyField(SerializedProperty property, FontStyle fontStyle = FontStyle.Normal) + private protected void DrawPropertyField(SerializedProperty property, FontStyle fontStyle = FontStyle.Normal) { + var originalWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = originalWidth - (m_IndentOffset * m_IndentLevel); var originalLabelFontStyle = EditorStyles.label.fontStyle; EditorStyles.label.fontStyle = fontStyle; EditorGUILayout.PropertyField(property); EditorStyles.label.fontStyle = originalLabelFontStyle; - } - - /// - /// Draws an indented with the option to provide the font style to use. - /// - /// - /// For additional information:
- /// -
- /// -
- ///
- /// The serialized property () to draw within the inspector view. - /// The font style () to use when drawing the label of the property field. - protected void DrawIndentedPropertyField(SerializedProperty property, FontStyle fontStyle = FontStyle.Normal) - { - var originalWidth = EditorGUIUtility.labelWidth; - EditorGUIUtility.labelWidth = originalWidth - (IndentOffset * IndentLevel); - DrawPropertyField(property, fontStyle); EditorGUIUtility.labelWidth = originalWidth; } @@ -64,15 +50,15 @@ protected void DrawIndentedPropertyField(SerializedProperty property, FontStyle /// You *must* call when returning back to the previous indention level.
/// For additional information:
/// -
- /// -
+ /// -
/// /// (optional) The size in pixels of the offset. If no value passed, then it uses a default of 15 pixels. - protected void BeginIndent(int offset = k_IndentOffset) + private protected void BeginIndent(int offset = k_IndentOffset) { - IndentOffset = offset; - IndentLevel++; + m_IndentOffset = offset; + m_IndentLevel++; GUILayout.BeginHorizontal(); - GUILayout.Space(IndentOffset); + GUILayout.Space(m_IndentOffset); GUILayout.BeginVertical(); } @@ -82,16 +68,16 @@ protected void BeginIndent(int offset = k_IndentOffset) /// /// For additional information:
/// -
- /// -
+ /// -
///
- protected void EndIndent() + private protected void EndIndent() { - if (IndentLevel == 0) + if (m_IndentLevel == 0) { Debug.LogWarning($"Invoking {nameof(EndIndent)} when the indent level is already 0!"); return; } - IndentLevel--; + m_IndentLevel--; GUILayout.EndVertical(); GUILayout.EndHorizontal(); } @@ -113,9 +99,11 @@ protected void DrawFoldOutGroup(Type type, Action displayProperties, bool exp EditorGUI.BeginChangeCheck(); serializedObject.Update(); var currentClass = typeof(T); + if (type.IsSubclassOf(currentClass) || (!type.IsSubclassOf(currentClass) && currentClass.IsSubclassOf(typeof(TT)))) { var expandedValue = EditorGUILayout.BeginFoldoutHeaderGroup(expanded, $"{currentClass.Name} Properties"); + if (expandedValue) { EditorGUILayout.EndFoldoutHeaderGroup(); diff --git a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs index 4370d5ca56..b04ad3f724 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -197,35 +197,35 @@ private void DisplayNetworkTransformProperties() BeginIndent(); if (networkTransform.SynchronizePosition) { - DrawIndentedPropertyField(m_PositionInterpolationTypeProperty); + DrawPropertyField(m_PositionInterpolationTypeProperty); // Only display when using Lerp. if (networkTransform.PositionInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { BeginIndent(); - DrawIndentedPropertyField(m_SlerpPosition); - DrawIndentedPropertyField(m_PositionMaximumInterpolationTimeProperty); + DrawPropertyField(m_SlerpPosition); + DrawPropertyField(m_PositionMaximumInterpolationTimeProperty); EndIndent(); } } if (networkTransform.SynchronizeRotation) { - DrawIndentedPropertyField(m_RotationInterpolationTypeProperty); + DrawPropertyField(m_RotationInterpolationTypeProperty); // Only display when using Lerp. if (networkTransform.RotationInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { BeginIndent(); - DrawIndentedPropertyField(m_RotationMaximumInterpolationTimeProperty); + DrawPropertyField(m_RotationMaximumInterpolationTimeProperty); EndIndent(); } } if (networkTransform.SynchronizeScale) { - DrawIndentedPropertyField(m_ScaleInterpolationTypeProperty); + DrawPropertyField(m_ScaleInterpolationTypeProperty); // Only display when using Lerp. if (networkTransform.ScaleInterpolationType == NetworkTransform.InterpolationTypes.Lerp) { BeginIndent(); - DrawIndentedPropertyField(m_ScaleMaximumInterpolationTimeProperty); + DrawPropertyField(m_ScaleMaximumInterpolationTimeProperty); EndIndent(); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 031151818e..045e942063 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -156,7 +156,7 @@ public void Reset(T currentValue) private int m_NbItemsReceivedThisFrame; private double m_LastMeasurementAddedTime = 0.0f; - internal bool EndOfBuffer => m_Buffer.Count == 0; + internal bool EndOfBuffer => m_BufferQueue.Count == 0; internal bool InLocalSpace; @@ -168,7 +168,37 @@ public void Reset(T currentValue) /// /// The current buffered items received by the authority. /// - protected internal readonly Queue m_Buffer = new Queue(k_BufferCountLimit); + protected internal readonly Queue m_BufferQueue = new Queue(k_BufferCountLimit); + + /// + /// The legacy list of items. + /// + /// + /// This is replaced by the of type . + /// + [Obsolete("This list is no longer used and will be deprecated.", false)] + protected internal readonly List m_Buffer = new List(); + + /// + /// ** Deprecating ** + /// The starting value of type to interpolate from. + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_InterpStartValue; + + /// + /// ** Deprecating ** + /// The current value of type . + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_CurrentInterpValue; + + /// + /// ** Deprecating ** + /// The end (or target) value of type to interpolate towards. + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_InterpEndValue; /// /// Represents the rate of change for the value being interpolated when smooth dampening is enabled. @@ -192,7 +222,7 @@ public void Reset(T currentValue) /// public void Clear() { - m_Buffer.Clear(); + m_BufferQueue.Clear(); m_BufferCount = 0; m_LastMeasurementAddedTime = 0.0; InterpolateState.Reset(default); @@ -244,7 +274,7 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m BufferedItem? previousItem = null; var startTime = 0.0; var alreadyHasBufferItem = false; - while (m_Buffer.TryPeek(out BufferedItem potentialItem)) + while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) { // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing // to consume. @@ -261,7 +291,7 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m if (!InterpolateState.Target.HasValue || ((potentialItem.TimeSent <= renderTime) && InterpolateState.Target.Value.TimeSent <= potentialItem.TimeSent)) { - if (m_Buffer.TryDequeue(out BufferedItem target)) + if (m_BufferQueue.TryDequeue(out BufferedItem target)) { if (!InterpolateState.Target.HasValue) { @@ -316,7 +346,7 @@ private void TryConsumeFromBuffer(double renderTime, float minDeltaTime, float m /// The minimum time delta between the current and target value. /// The maximum time delta between the current and target value. /// The newly interpolated value of type 'T' - public T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, float maxDeltaTime) + internal T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, float maxDeltaTime) { TryConsumeFromBuffer(tickLatencyAsTime, minDeltaTime, maxDeltaTime); // Only interpolate when there is a start and end point and we have not already reached the end value @@ -353,7 +383,7 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) { BufferedItem? previousItem = null; var alreadyHasBufferItem = false; - while (m_Buffer.TryPeek(out BufferedItem potentialItem)) + while (m_BufferQueue.TryPeek(out BufferedItem potentialItem)) { // If we are still on the same buffered item (FIFO Queue), then exit early as there is nothing // to consume. @@ -365,12 +395,11 @@ private void TryConsumeFromBuffer(double renderTime, double serverTime) if ((potentialItem.TimeSent <= serverTime) && (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) { - if (m_Buffer.TryDequeue(out BufferedItem target)) + if (m_BufferQueue.TryDequeue(out BufferedItem target)) { if (!InterpolateState.Target.HasValue) { InterpolateState.Target = target; - alreadyHasBufferItem = true; InterpolateState.PreviousValue = InterpolateState.CurrentValue; InterpolateState.StartTime = target.TimeSent; @@ -494,7 +523,7 @@ public void AddMeasurement(T newMeasurement, double sentTime) m_LastMeasurementAddedTime = sentTime; m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); // Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues - m_Buffer.Enqueue(m_LastBufferedItemReceived); + m_BufferQueue.Enqueue(m_LastBufferedItemReceived); } return; } @@ -504,7 +533,7 @@ public void AddMeasurement(T newMeasurement, double sentTime) { m_BufferCount++; m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); - m_Buffer.Enqueue(m_LastBufferedItemReceived); + m_BufferQueue.Enqueue(m_LastBufferedItemReceived); m_LastMeasurementAddedTime = sentTime; } } @@ -547,7 +576,7 @@ public T GetInterpolatedValue() /// The increasing delta time from when start to finish. /// Maximum rate of change per pass. /// The smoothed value. - protected internal virtual T SmoothDamp(T current, T target, ref T rateOfChange, float duration, float deltaTime, float maxSpeed = Mathf.Infinity) + private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange, float duration, float deltaTime, float maxSpeed = Mathf.Infinity) { return target; } @@ -559,7 +588,7 @@ protected internal virtual T SmoothDamp(T current, T target, ref T rateOfChange, /// Second value of type . /// The precision of the aproximation. /// true if the two values are aproximately the same and false if they are not - protected internal virtual bool IsAproximately(T first, T second, float precision = k_AproximatePrecision) + private protected virtual bool IsAproximately(T first, T second, float precision = k_AproximatePrecision) { return false; } @@ -578,12 +607,12 @@ protected internal virtual T OnConvertTransformSpace(Transform transform, T item internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) { - var count = m_Buffer.Count; + var count = m_BufferQueue.Count; for (int i = 0; i < count; i++) { - var entry = m_Buffer.Dequeue(); + var entry = m_BufferQueue.Dequeue(); entry.Item = OnConvertTransformSpace(transform, entry.Item, inLocalSpace); - m_Buffer.Enqueue(entry); + m_BufferQueue.Enqueue(entry); } InterpolateState.CurrentValue = OnConvertTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); var end = InterpolateState.Target.Value; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs index 7429682c49..a6a306dd91 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs @@ -21,13 +21,13 @@ protected override float Interpolate(float start, float end, float time) } /// - protected internal override bool IsAproximately(float first, float second, float precision = 1E-07F) + private protected override bool IsAproximately(float first, float second, float precision = 1E-07F) { return Mathf.Approximately(first, second); } /// - protected internal override float SmoothDamp(float current, float target, ref float rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) + private protected override float SmoothDamp(float current, float target, ref float rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) { if (IsAngularValue) { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs index 8e75dec162..8eb6d29bcc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs @@ -47,7 +47,7 @@ protected override Quaternion Interpolate(Quaternion start, Quaternion end, floa } /// - protected internal override Quaternion SmoothDamp(Quaternion current, Quaternion target, ref Quaternion rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) + private protected override Quaternion SmoothDamp(Quaternion current, Quaternion target, ref Quaternion rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) { Vector3 currentEuler = current.eulerAngles; Vector3 targetEuler = target.eulerAngles; @@ -61,7 +61,7 @@ protected internal override Quaternion SmoothDamp(Quaternion current, Quaternion } /// - protected internal override bool IsAproximately(Quaternion first, Quaternion second, float precision) + private protected override bool IsAproximately(Quaternion first, Quaternion second, float precision) { return (1.0f - Quaternion.Dot(first, second)) <= precision; } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs index 59be639e00..0a0e255236 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs @@ -53,13 +53,13 @@ protected internal override Vector3 OnConvertTransformSpace(Transform transform, } /// - protected internal override bool IsAproximately(Vector3 first, Vector3 second, float precision = 0.0001F) + private protected override bool IsAproximately(Vector3 first, Vector3 second, float precision = 0.0001F) { return Vector3.Distance(first, second) <= precision; } /// - protected internal override Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 rateOfChange, float duration, float deltaTime, float maxSpeed) + private protected override Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 rateOfChange, float duration, float deltaTime, float maxSpeed) { if (IsAngularValue) { From 4fe72f576fc9c873a2db3deb6c6bc09cd8b3273a Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 7 Mar 2025 18:00:36 -0600 Subject: [PATCH 41/41] update Some more adjustments to permissions and some final clean up of BufferedLinearInterpolator --- .../BufferedLinearInterpolator.cs | 174 ++++++++++-------- .../BufferedLinearInterpolatorFloat.cs | 2 +- .../BufferedLinearInterpolatorVector3.cs | 2 +- 3 files changed, 97 insertions(+), 81 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 045e942063..0aabd0226e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -11,7 +11,68 @@ namespace Unity.Netcode /// The type of interpolated value public abstract class BufferedLinearInterpolator where T : struct { + // Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so + // that we don't have a very small buffer because of this. + private const int k_BufferCountLimit = 100; private const float k_AproximatePrecision = 0.0001f; + private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator + + #region Legacy notes + // Buffer consumption scenarios + // Perfect case consumption + // | 1 | 2 | 3 | + // | 2 | 3 | 4 | consume 1 + // | 3 | 4 | 5 | consume 2 + // | 4 | 5 | 6 | consume 3 + // | 5 | 6 | 7 | consume 4 + // jittered case + // | 1 | 2 | 3 | + // | 2 | 3 | | consume 1 + // | 3 | | | consume 2 + // | 4 | 5 | 6 | consume 3 + // | 5 | 6 | 7 | consume 4 + // bursted case (assuming max count is 5) + // | 1 | 2 | 3 | + // | 2 | 3 | | consume 1 + // | 3 | | | consume 2 + // | | | | consume 3 + // | | | | + // | 4 | 5 | 6 | 7 | 8 | --> consume all and teleport to last value <8> --> this is the nuclear option, ideally this example would consume 4 and 5 + // instead of jumping to 8, but since in OnValueChange we don't yet have an updated server time (updated in pre-update) to know which value + // we should keep and which we should drop, we don't have enough information to do this. Another thing would be to not have the burst in the first place. + #endregion + + #region Properties being deprecated + /// + /// The legacy list of items. + /// + /// + /// This is replaced by the of type . + /// + [Obsolete("This list is no longer used and will be deprecated.", false)] + protected internal readonly List m_Buffer = new List(); + + /// + /// ** Deprecating ** + /// The starting value of type to interpolate from. + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_InterpStartValue; + + /// + /// ** Deprecating ** + /// The current value of type . + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_CurrentInterpValue; + + /// + /// ** Deprecating ** + /// The end (or target) value of type to interpolate towards. + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_InterpEndValue; + #endregion /// /// Represents a buffered item measurement. @@ -44,6 +105,13 @@ public BufferedItem(T item, double timeSent, int itemId) ItemId = itemId; } } + + /// + /// The current internal state of the . + /// + /// + /// Not public API ready yet. + /// internal struct CurrentState { public BufferedItem? Target; @@ -105,35 +173,6 @@ public void Reset(T currentValue) } } - // Buffer consumption scenarios - // Perfect case consumption - // | 1 | 2 | 3 | - // | 2 | 3 | 4 | consume 1 - // | 3 | 4 | 5 | consume 2 - // | 4 | 5 | 6 | consume 3 - // | 5 | 6 | 7 | consume 4 - // jittered case - // | 1 | 2 | 3 | - // | 2 | 3 | | consume 1 - // | 3 | | | consume 2 - // | 4 | 5 | 6 | consume 3 - // | 5 | 6 | 7 | consume 4 - // bursted case (assuming max count is 5) - // | 1 | 2 | 3 | - // | 2 | 3 | | consume 1 - // | 3 | | | consume 2 - // | | | | consume 3 - // | | | | - // | 4 | 5 | 6 | 7 | 8 | --> consume all and teleport to last value <8> --> this is the nuclear option, ideally this example would consume 4 and 5 - // instead of jumping to 8, but since in OnValueChange we don't yet have an updated server time (updated in pre-update) to know which value - // we should keep and which we should drop, we don't have enough information to do this. Another thing would be to not have the burst in the first place. - - // Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so - // that we don't have a very small buffer because of this. - private const int k_BufferCountLimit = 100; - - private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator - /// /// Determines how much smoothing will be applied to the 2nd lerp when using the (i.e. lerping and not smooth dampening). /// @@ -145,61 +184,27 @@ public void Reset(T currentValue) [Range(0.016f, 1.0f)] public float MaximumInterpolationTime = 0.1f; - /// - /// The maximum Lerp "t" boundary when using standard lerping for interpolation - /// - internal float MaxInterpolationBound = 3.0f; - - private int m_BufferCount; - - private BufferedItem m_LastBufferedItemReceived; - private int m_NbItemsReceivedThisFrame; - - private double m_LastMeasurementAddedTime = 0.0f; - internal bool EndOfBuffer => m_BufferQueue.Count == 0; - - internal bool InLocalSpace; - - /// - /// The current interpolation state - /// - internal CurrentState InterpolateState; - /// /// The current buffered items received by the authority. /// protected internal readonly Queue m_BufferQueue = new Queue(k_BufferCountLimit); /// - /// The legacy list of items. - /// - /// - /// This is replaced by the of type . - /// - [Obsolete("This list is no longer used and will be deprecated.", false)] - protected internal readonly List m_Buffer = new List(); - - /// - /// ** Deprecating ** - /// The starting value of type to interpolate from. - /// - [Obsolete("This property will be deprecated.", false)] - protected internal T m_InterpStartValue; - - /// - /// ** Deprecating ** - /// The current value of type . + /// The current interpolation state /// - [Obsolete("This property will be deprecated.", false)] - protected internal T m_CurrentInterpValue; + internal CurrentState InterpolateState; /// - /// ** Deprecating ** - /// The end (or target) value of type to interpolate towards. + /// The maximum Lerp "t" boundary when using standard lerping for interpolation /// - [Obsolete("This property will be deprecated.", false)] - protected internal T m_InterpEndValue; + internal float MaxInterpolationBound = 3.0f; + internal bool EndOfBuffer => m_BufferQueue.Count == 0; + internal bool InLocalSpace; + private double m_LastMeasurementAddedTime = 0.0f; + private int m_BufferCount; + private int m_NbItemsReceivedThisFrame; + private BufferedItem m_LastBufferedItemReceived; /// /// Represents the rate of change for the value being interpolated when smooth dampening is enabled. /// @@ -210,12 +215,10 @@ public void Reset(T currentValue) /// private T m_PredictedRateOfChange; - private bool m_IsAngularValue; - /// /// When true, the value is an angular numeric representation. /// - protected bool IsAngularValue => m_IsAngularValue; + private protected bool m_IsAngularValue; /// /// Resets interpolator to the defaults. @@ -237,10 +240,12 @@ public void Clear() /// This is used when first synchronizing/initializing and when telporting an object. /// /// The target value to reset the interpolator to - /// The current server time + /// The current server time /// When rotation is expressed as Euler values (i.e. Vector3 and/or float) this helps determine what kind of smooth dampening to use. public void ResetTo(T targetValue, double serverTime, bool isAngularValue = false) { + // Clear the interpolator + Clear(); InternalReset(targetValue, serverTime, isAngularValue); } @@ -518,8 +523,8 @@ public void AddMeasurement(T newMeasurement, double sentTime) { // Clear the interpolator Clear(); - // Reset to the new value - InternalReset(newMeasurement, sentTime, IsAngularValue, false); + // Reset to the new value but don't automatically add the measurement (prevents recursion) + InternalReset(newMeasurement, sentTime, m_IsAngularValue, false); m_LastMeasurementAddedTime = sentTime; m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); // Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues @@ -569,6 +574,9 @@ public T GetInterpolatedValue() /// /// An alternate smoothing method to Lerp. /// + /// + /// Not public API ready yet. + /// /// Current item value. /// Target item value. /// The velocity of change. @@ -584,6 +592,9 @@ private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange, /// /// Determines if two values of type are close to the same value. /// + /// + /// Not public API ready yet. + /// /// First value of type . /// Second value of type . /// The precision of the aproximation. @@ -605,6 +616,11 @@ protected internal virtual T OnConvertTransformSpace(Transform transform, T item return default; } + /// + /// Invoked by when the transform has transitioned between local to world or vice versa. + /// + /// The transform that the is associated with. + /// Whether the is now being tracked in local or world spaced. internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) { var count = m_BufferQueue.Count; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs index a6a306dd91..9d3765fb40 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorFloat.cs @@ -29,7 +29,7 @@ private protected override bool IsAproximately(float first, float second, float /// private protected override float SmoothDamp(float current, float target, ref float rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) { - if (IsAngularValue) + if (m_IsAngularValue) { return Mathf.SmoothDampAngle(current, target, ref rateOfChange, duration, maxSpeed, deltaTime); } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs index 0a0e255236..2bb32d5402 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorVector3.cs @@ -61,7 +61,7 @@ private protected override bool IsAproximately(Vector3 first, Vector3 second, fl /// private protected override Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 rateOfChange, float duration, float deltaTime, float maxSpeed) { - if (IsAngularValue) + if (m_IsAngularValue) { current.x = Mathf.SmoothDampAngle(current.x, target.x, ref rateOfChange.x, duration, maxSpeed, deltaTime); current.y = Mathf.SmoothDampAngle(current.y, target.y, ref rateOfChange.y, duration, maxSpeed, deltaTime);