diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9a9a08bc6f..1bffdbce19 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 `ChangeOwnership` changing ownership to clients that are not observers. This also happened with automated object distribution. (#3323) - 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) @@ -31,6 +35,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) - Ensured that a useful error is thrown when attempting to build a dedicated server with Unity Transport that uses websockets. (#3336) - 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) 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 4b8a351a35..b61a77e6b6 100644 --- a/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs +++ b/com.unity.netcode.gameobjects/Editor/NetcodeEditorBase.cs @@ -8,18 +8,87 @@ 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; + private int m_IndentOffset = 0; + private int m_IndentLevel = 0; + private float m_OriginalLabelWidth; + /// public virtual void OnEnable() { } + /// + /// Draws a with the option to provide the font style to use. + /// + /// The serialized property () to draw within the inspector view. + /// The font style () to use when drawing the label of the property field. + 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; + 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. + private protected void BeginIndent(int offset = k_IndentOffset) + { + m_IndentOffset = offset; + m_IndentLevel++; + GUILayout.BeginHorizontal(); + GUILayout.Space(m_IndentOffset); + GUILayout.BeginVertical(); + } + + /// + /// Will end the current indention level when drawing propery fields. + /// + /// + /// For additional information:
+ /// -
+ /// -
+ ///
+ private protected void EndIndent() + { + if (m_IndentLevel == 0) + { + Debug.LogWarning($"Invoking {nameof(EndIndent)} when the indent level is already 0!"); + return; + } + m_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 @@ -30,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 e93226e2bd..b04ad3f724 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs @@ -28,6 +28,13 @@ public class NetworkTransformEditor : NetcodeEditorBase private SerializedProperty m_ScaleThresholdProperty; private SerializedProperty m_InLocalSpaceProperty; private SerializedProperty m_InterpolateProperty; + private SerializedProperty m_PositionInterpolationTypeProperty; + 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; @@ -36,6 +43,7 @@ public class NetworkTransformEditor : NetcodeEditorBase private SerializedProperty m_AuthorityMode; private static int s_ToggleOffset = 45; + 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"); @@ -61,6 +69,15 @@ 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_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)); m_UseQuaternionCompression = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionCompression)); m_UseHalfFloatPrecision = serializedObject.FindProperty(nameof(NetworkTransform.UseHalfFloatPrecision)); @@ -141,9 +158,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); @@ -158,9 +187,53 @@ 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) + { + DrawPropertyField(m_PositionInterpolationTypeProperty); + // Only display when using Lerp. + if (networkTransform.PositionInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + { + BeginIndent(); + DrawPropertyField(m_SlerpPosition); + DrawPropertyField(m_PositionMaximumInterpolationTimeProperty); + EndIndent(); + } + } + if (networkTransform.SynchronizeRotation) + { + DrawPropertyField(m_RotationInterpolationTypeProperty); + // Only display when using Lerp. + if (networkTransform.RotationInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + { + BeginIndent(); + DrawPropertyField(m_RotationMaximumInterpolationTimeProperty); + EndIndent(); + } + } + if (networkTransform.SynchronizeScale) + { + DrawPropertyField(m_ScaleInterpolationTypeProperty); + // Only display when using Lerp. + if (networkTransform.ScaleInterpolationType == NetworkTransform.InterpolationTypes.Lerp) + { + BeginIndent(); + DrawPropertyField(m_ScaleMaximumInterpolationTimeProperty); + EndIndent(); + } + } + EndIndent(); + EditorGUILayout.Space(); + } } - EditorGUILayout.PropertyField(m_SlerpPosition); + EditorGUILayout.PropertyField(m_UseQuaternionSynchronization); if (m_UseQuaternionSynchronization.boolValue) { @@ -190,8 +263,6 @@ private void DisplayNetworkTransformProperties() #endif // COM_UNITY_MODULES_PHYSICS2D } - - /// public override void OnInspectorGUI() { 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 d5fbaaa5cf..0aabd0226e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -11,37 +11,13 @@ namespace Unity.Netcode /// The type of interpolated value public abstract class BufferedLinearInterpolator where T : struct { - internal float MaxInterpolationBound = 3.0f; - protected internal struct BufferedItem - { - public T Item; - public double TimeSent; - - public BufferedItem(T item, double timeSent) - { - Item = item; - TimeSent = timeSent; - } - } - - /// - /// 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; - + // 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 - 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); - - - + #region Legacy notes // Buffer consumption scenarios // Perfect case consumption // | 1 | 2 | 3 | @@ -64,130 +40,410 @@ public BufferedItem(T item, double timeSent) // | 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 - // 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 BufferedItem m_LastBufferedItemReceived; - private int m_NbItemsReceivedThisFrame; - - private int m_LifetimeConsumedCount; + #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(); - private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0; + /// + /// ** Deprecating ** + /// The starting value of type to interpolate from. + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_InterpStartValue; - internal bool EndOfBuffer => m_Buffer.Count == 0; + /// + /// ** Deprecating ** + /// The current value of type . + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_CurrentInterpValue; - internal bool InLocalSpace; + /// + /// ** Deprecating ** + /// The end (or target) value of type to interpolate towards. + /// + [Obsolete("This property will be deprecated.", false)] + protected internal T m_InterpEndValue; + #endregion - protected internal virtual void OnConvertTransformSpace(Transform transform, bool inLocalSpace) + /// + /// Represents a buffered item measurement. + /// + 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; + TimeSent = timeSent; + ItemId = itemId; + } } - internal void ConvertTransformSpace(Transform transform, bool inLocalSpace) + /// + /// The current internal state of the . + /// + /// + /// Not public API ready yet. + /// + internal struct CurrentState { - OnConvertTransformSpace(transform, inLocalSpace); - InLocalSpace = inLocalSpace; + public BufferedItem? Target; + + public double StartTime; + public double EndTime; + public float TimeToTargetValue; + public float DeltaTime; + public float LerpT; + + public T CurrentValue; + public T PreviousValue; + + private float m_AverageDeltaTime; + + public float AverageDeltaTime => m_AverageDeltaTime; + public float FinalTimeToTarget => TimeToTargetValue - DeltaTime; + + public void AddDeltaTime(float deltaTime) + { + if (m_AverageDeltaTime == 0.0f) + { + m_AverageDeltaTime = deltaTime; + } + else + { + m_AverageDeltaTime += deltaTime; + m_AverageDeltaTime *= 0.5f; + } + 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() + { + if (!Target.HasValue) + { + return false; + } + return m_AverageDeltaTime >= FinalTimeToTarget; + } + + public void Reset(T currentValue) + { + Target = null; + CurrentValue = currentValue; + PreviousValue = currentValue; + // When reset, we consider ourselves to have already arrived at the target (even if no target is set) + LerpT = 0.0f; + EndTime = 0.0; + StartTime = 0.0; + ResetDelta(); + } } /// - /// Resets interpolator to initial state + /// 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; + + /// + /// The current buffered items received by the authority. + /// + protected internal readonly Queue m_BufferQueue = new Queue(k_BufferCountLimit); + + /// + /// The current interpolation state + /// + internal CurrentState InterpolateState; + + /// + /// The maximum Lerp "t" boundary when using standard lerping for interpolation + /// + 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. + /// + private T m_RateOfChange; + + /// + /// Represents the predicted rate of change for the value being interpolated when smooth dampening is enabled. + /// + private T m_PredictedRateOfChange; + + /// + /// When true, the value is an angular numeric representation. + /// + private protected bool m_IsAngularValue; + + /// + /// Resets interpolator to the defaults. /// public void Clear() { - m_Buffer.Clear(); - m_EndTimeConsumed = 0.0d; - m_StartTimeConsumed = 0.0d; + m_BufferQueue.Clear(); + m_BufferCount = 0; + m_LastMeasurementAddedTime = 0.0; + InterpolateState.Reset(default); + m_RateOfChange = default; + m_PredictedRateOfChange = default; } /// - /// Teleports current interpolation value to targetValue. + /// Resets the current interpolator to the target value. /// - /// 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) + /// 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) { - m_LifetimeConsumedCount = 1; - m_InterpStartValue = targetValue; - m_InterpEndValue = targetValue; - m_CurrentInterpValue = targetValue; - m_Buffer.Clear(); - m_EndTimeConsumed = 0.0d; - m_StartTimeConsumed = 0.0d; - - Update(0, serverTime, serverTime); + // Clear the interpolator + Clear(); + InternalReset(targetValue, serverTime, isAngularValue); } - // 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) + private void InternalReset(T targetValue, double serverTime, bool isAngularValue = false, bool addMeasurement = true) { - 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) + m_RateOfChange = default; + m_PredictedRateOfChange = default; + // Set our initial value + InterpolateState.Reset(targetValue); + m_IsAngularValue = isAngularValue; + + if (addMeasurement) { - 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 + // Add the first measurement for our baseline + AddMeasurement(targetValue, serverTime); + } + } - // 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 + #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) + { + 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; + var alreadyHasBufferItem = false; + while (m_BufferQueue.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) + { + 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 (!itemToInterpolateTo.HasValue || bufferedValue.TimeSent > itemToInterpolateTo.Value.TimeSent) + if (m_BufferQueue.TryDequeue(out BufferedItem target)) { - if (m_LifetimeConsumedCount == 0) - { - // if interpolator not initialized, teleport to first value when available - m_StartTimeConsumed = bufferedValue.TimeSent; - m_InterpStartValue = bufferedValue.Item; - } - else if (consumedCount == 0) + if (!InterpolateState.Target.HasValue) { - // 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; - } + InterpolateState.Target = target; - if (bufferedValue.TimeSent > m_EndTimeConsumed) + alreadyHasBufferItem = true; + InterpolateState.PreviousValue = InterpolateState.CurrentValue; + InterpolateState.TimeToTargetValue = minDeltaTime; + startTime = InterpolateState.Target.Value.TimeSent; + } + else { - itemToInterpolateTo = bufferedValue; - m_EndTimeConsumed = bufferedValue.TimeSent; - m_InterpEndValue = bufferedValue.Item; + 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.ResetDelta(); } + } + else + { + break; + } - m_Buffer.RemoveAt(i); - consumedCount++; - m_LifetimeConsumedCount++; + 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) + /// Interpolation Update to use when smooth dampening is enabled on a . /// - /// time since call - /// current server time + /// + /// 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. + /// 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, NetworkTime serverTime) + internal T Update(float deltaTime, double tickLatencyAsTime, float minDeltaTime, float maxDeltaTime) { - return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); + 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) + { + InterpolateState.AddDeltaTime(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 * 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, deltaTime); + } + 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_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. + if (previousItem.HasValue && previousItem.Value.TimeSent == potentialItem.TimeSent) + { + break; + } + + if ((potentialItem.TimeSent <= serverTime) && + (!InterpolateState.Target.HasValue || InterpolateState.Target.Value.TimeSent < potentialItem.TimeSent)) + { + if (m_BufferQueue.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; + } + } } /// - /// Call to update the state of the interpolators before reading out + /// 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 @@ -195,36 +451,19 @@ public T Update(float deltaTime, NetworkTime serverTime) 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"); - } - - // 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.Target.HasValue) { + // The original BufferedLinearInterpolator lerping script to assure the Smooth Dampening updates do not impact + // this specific behavior. float t = 1.0f; - double range = m_EndTimeConsumed - m_StartTimeConsumed; + double range = InterpolateState.EndTime - InterpolateState.StartTime; if (range > k_SmallValue) { - var rangeFactor = 1.0f / (float)range; - - t = ((float)renderTime - (float)m_StartTimeConsumed) * rangeFactor; + t = (float)((renderTime - InterpolateState.StartTime) / range); if (t < 0.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; } @@ -234,13 +473,37 @@ public T Update(float deltaTime, double renderTime, double serverTime) t = 1.0f; } } + var target = Interpolate(InterpolateState.PreviousValue, InterpolateState.Target.Value.Item, t); - var target = InterpolateUnclamped(m_InterpStartValue, m_InterpEndValue, t); - m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / MaximumInterpolationTime); // second interpolate to smooth out extrapolation jumps + // 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 m_CurrentInterpValue; + return InterpolateState.CurrentValue; + } + #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 + /// + internal T UpdateInternal(float deltaTime, NetworkTime serverTime) + { + return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time); } /// @@ -258,21 +521,25 @@ public void AddMeasurement(T newMeasurement, double sentTime) { if (m_LastBufferedItemReceived.TimeSent < sentTime) { - m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); - ResetTo(newMeasurement, sentTime); + // Clear the interpolator + Clear(); + // 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 - m_Buffer.Add(m_LastBufferedItemReceived); + m_BufferQueue.Enqueue(m_LastBufferedItemReceived); } - 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_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now + // Drop measurements that are received out of order/late + if (sentTime > m_LastMeasurementAddedTime || m_BufferCount == 0) { - m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime); - m_Buffer.Add(m_LastBufferedItemReceived); + m_BufferCount++; + m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime, m_BufferCount); + m_BufferQueue.Enqueue(m_LastBufferedItemReceived); + m_LastMeasurementAddedTime = sentTime; } } @@ -282,7 +549,7 @@ public void AddMeasurement(T newMeasurement, double sentTime) /// The current interpolated value of type 'T' public T GetInterpolatedValue() { - return m_CurrentInterpValue; + return InterpolateState.CurrentValue; } /// @@ -302,166 +569,72 @@ public T GetInterpolatedValue() /// 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.Lerp(start, end, time); - } - - /// - 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 + /// An alternate smoothing method to Lerp. /// /// - /// 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. + /// Not public API ready yet. /// - public bool IsSlerp; - - /// - protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time) - { - if (IsSlerp) - { - return Quaternion.Slerp(start, end, time); - } - else - { - return Quaternion.Lerp(start, end, time); - } - } - - /// - protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time) + /// 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. + private protected virtual T SmoothDamp(T current, T target, ref T rateOfChange, float duration, float deltaTime, float maxSpeed = Mathf.Infinity) { - if (IsSlerp) - { - return Quaternion.Slerp(start, end, time); - } - else - { - return Quaternion.Lerp(start, end, time); - } + return target; } - 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) - { - for (int i = 0; i < m_Buffer.Count; i++) - { - var entry = m_Buffer[i]; - entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace); - m_Buffer[i] = entry; - } - - m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace); - m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); - m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace); - - base.OnConvertTransformSpace(transform, inLocalSpace); - } - } - - /// - /// A implementation. - /// - public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator - { /// - /// Use when . - /// Use when + /// Determines if two values of type are close to the same value. /// - public bool IsSlerp; - /// - protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, float time) + /// + /// Not public API ready yet. + /// + /// 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 + private protected virtual bool IsAproximately(T first, T second, float precision = k_AproximatePrecision) { - if (IsSlerp) - { - return Vector3.Slerp(start, end, time); - } - else - { - return Vector3.Lerp(start, end, time); - } + return false; } - /// - 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) + /// + /// Converts a value of type from world to local space or vice versa. + /// + /// 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) { - if (inLocalSpace) - { - return transform.InverseTransformPoint(position); - - } - else - { - return transform.TransformPoint(position); - } + return default; } - protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace) + /// + /// 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) { - for (int i = 0; i < m_Buffer.Count; i++) + var count = m_BufferQueue.Count; + for (int i = 0; i < count; i++) { - var entry = m_Buffer[i]; - entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace); - m_Buffer[i] = entry; + var entry = m_BufferQueue.Dequeue(); + entry.Item = OnConvertTransformSpace(transform, entry.Item, inLocalSpace); + m_BufferQueue.Enqueue(entry); } - - m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace); - m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace); - m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace); - - base.OnConvertTransformSpace(transform, inLocalSpace); + InterpolateState.CurrentValue = OnConvertTransformSpace(transform, InterpolateState.CurrentValue, inLocalSpace); + var end = InterpolateState.Target.Value; + end.Item = OnConvertTransformSpace(transform, end.Item, inLocalSpace); + InterpolateState.Target = end; + InLocalSpace = inLocalSpace; } } } 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..9d3765fb40 --- /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); + } + + /// + private protected override bool IsAproximately(float first, float second, float precision = 1E-07F) + { + return Mathf.Approximately(first, second); + } + + /// + private protected override float SmoothDamp(float current, float target, ref float rateOfChange, float duration, float deltaTime, float maxSpeed = float.PositiveInfinity) + { + if (m_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..8eb6d29bcc --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolatorQuaternion.cs @@ -0,0 +1,82 @@ +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); + } + } + + /// + 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; + 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); + } + + /// + private protected override bool IsAproximately(Quaternion first, Quaternion second, float precision) + { + return (1.0f - Quaternion.Dot(first, second)) <= 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..2bb32d5402 --- /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); + } + } + + /// + private protected override bool IsAproximately(Vector3 first, Vector3 second, float precision = 0.0001F) + { + return Vector3.Distance(first, second) <= precision; + } + + /// + private protected override Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 rateOfChange, float duration, float deltaTime, float maxSpeed) + { + 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); + 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/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/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 7b2126b39d..d0951c6cfb 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 @@ -421,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; @@ -433,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; @@ -616,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 @@ -661,7 +660,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); - } else { @@ -937,11 +935,128 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade #endregion #region PROPERTIES AND GENERAL METHODS + + /// + /// 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 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 + } + + /// + /// The position interpolation type to use for the instance. + /// + /// + /// - 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. + /// + /// + /// - 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. + /// + /// + /// - 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; + + /// + /// 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 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 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 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 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 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 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; + + /// + /// 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.")] @@ -951,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. @@ -1049,7 +1163,7 @@ public enum AuthorityModes /// public bool SyncPositionZ = true; - private bool SynchronizePosition + internal bool SynchronizePosition { get { @@ -1084,7 +1198,7 @@ private bool SynchronizePosition /// public bool SyncRotAngleZ = true; - private bool SynchronizeRotation + internal bool SynchronizeRotation { get { @@ -1116,7 +1230,7 @@ private bool SynchronizeRotation /// public bool SyncScaleZ = true; - private bool SynchronizeScale + internal bool SynchronizeScale { get { @@ -1211,7 +1325,14 @@ private 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); /// @@ -1474,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; @@ -1785,11 +1902,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; @@ -1808,9 +1927,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 @@ -2236,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() { @@ -2849,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() { @@ -2874,7 +3005,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).Time; if (LogStateUpdate) { @@ -3130,12 +3261,24 @@ 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) + { + 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(); m_PositionInterpolator = new BufferedLinearInterpolatorVector3(); @@ -3395,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 @@ -3684,49 +3828,87 @@ 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; #else - var cachedDeltaTime = m_CachedNetworkManager.RealTimeProvider.DeltaTime; + var cachedDeltaTime = Time.deltaTime; #endif - // 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 tickLatency = m_CachedNetworkManager.NetworkTimeSystem.TickLatency; - // TODO: This could most likely just always be 2 - // var ticksAgo = ((!IsServerAuthoritative() && !IsServer) || m_CachedNetworkManager.DistributedAuthorityMode) && !m_CachedNetworkManager.DAHost ? 2 : 1; - var ticksAgo = 2; + // 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 cachedRenderTime = serverTime.TimeTicksAgo(ticksAgo).Time; + 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; + // 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 - if (SynchronizePosition) + // Now only update the interpolators for the portions of the transform being synchronized + if (SynchronizePosition) + { + if (PositionInterpolationType == InterpolationTypes.Lerp) + { + m_PositionInterpolator.MaximumInterpolationTime = PositionMaxInterpolationTime; + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + else { - m_PositionInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + m_PositionInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); } + } - if (SynchronizeRotation) + if (SynchronizeRotation) + { + if (RotationInterpolationType == InterpolationTypes.Lerp) { + 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, tickLatencyAsTime, cachedServerTime); + } + else + { + m_RotationInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); } + } - if (SynchronizeScale) + if (SynchronizeScale) + { + if (ScaleInterpolationType == InterpolationTypes.Lerp) { - m_ScaleInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + m_ScaleInterpolator.MaximumInterpolationTime = ScaleMaxInterpolationTime; + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, cachedServerTime); + } + else + { + m_ScaleInterpolator.Update(cachedDeltaTime, tickLatencyAsTime, minDeltaTime, maxDeltaTime); } } } @@ -3749,14 +3931,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 - /// /// When paired with a NetworkRigidbody and NetworkRigidbody.UseRigidBodyForMotion is enabled, /// this will be invoked during . @@ -3771,8 +3956,12 @@ public virtual void OnFixedUpdate() m_NetworkRigidbodyInternal.WakeIfSleeping(); - // Update interpolation - UpdateInterpolation(); + + // Update interpolation when enabled + if (Interpolate) + { + UpdateInterpolation(); + } // Apply the current authoritative state ApplyAuthoritativeState(); @@ -3932,17 +4121,21 @@ 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. + /// + /// Returns the tick latency and local offset in seconds and as a float value. public static float GetTickLatency() { return GetTickLatency(NetworkManager.Singleton); @@ -3950,9 +4143,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; } @@ -3960,6 +4153,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); @@ -3994,40 +4188,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) { @@ -4042,40 +4208,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; 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/Runtime/Timing/NetworkTime.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTime.cs index 916a35e6c4..c37c29fa75 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 a40c88ed0a..c3fc9e8b67 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,13 +76,26 @@ 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; private NetworkTickSystem m_NetworkTickSystem; private NetworkManager m_NetworkManager; + private double m_TickFrequency; /// /// @@ -101,6 +115,7 @@ public NetworkTimeSystem(double localBufferSec, double serverBufferSec = k_Defau ServerBufferSec = serverBufferSec; HardResetThresholdSec = hardResetThresholdSec; AdjustmentRatio = adjustmentRatio; + m_TickLatencyAverage = 2; } /// @@ -113,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) { @@ -203,7 +219,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_TickFrequency), (float)deltaTimeSec); + 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) { @@ -243,6 +266,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,7 +274,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; } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs index b59f9093de..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 @@ -43,29 +42,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,26 +85,25 @@ 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)); } - [Ignore("TODO: Fix this test to still handle testing message loss without extrapolation. This is tracked in MTT-11338")] [Test] public void MessageLoss() { @@ -123,66 +121,28 @@ 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); - 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); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(3f)); - - // interpolation continues as expected - serverTime += timeStep; // t = 4.5 - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(3.5f)); - - serverTime += timeStep; // t = 5 - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(4f)); - - // lost time=6, extrapolating - serverTime += timeStep; // t = 5.5 - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(4.5f)); - - serverTime += timeStep; // t = 6.0 - interpolator.Update((float)timeStep, serverTime); - Assert.That(interpolator.GetInterpolatedValue(), Is.EqualTo(5f)); - - // misprediction - serverTime += timeStep; // t = 6.5 - interpolator.Update((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); - 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] @@ -195,21 +155,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 +183,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 +208,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 +216,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 +241,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 +253,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)); } @@ -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.Update(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() { @@ -323,39 +282,24 @@ 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); - 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); - Assert.That(interp, Is.EqualTo(2.5f)); - - serverTime += 0.5d; // t = 4 - interp = interpolator.Update(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)); + // Since there is no extrapolation, the rest of this test was removed. } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs index 3a7d0be8c7..efb1ce1244 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformGeneral.cs @@ -4,18 +4,31 @@ 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 OnOneTimeTearDown() + { + m_EnableVerboseDebug = false; + NetworkTransform.AssignDefaultInterpolationType = false; + NetworkTransform.DefaultInterpolationType = NetworkTransform.InterpolationTypes.Lerp; + base.OnOneTimeTearDown(); + } + /// /// Test to verify nonAuthority cannot change the transform directly /// @@ -268,40 +281,45 @@ 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; + 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)); + 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)); + 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!"); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 7e12934a67..6418e0f23f 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,19 @@ 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 void OnOneTimeTearDown() + { + NetworkTransform.AssignDefaultInterpolationType = false; + NetworkTransform.DefaultInterpolationType = NetworkTransform.InterpolationTypes.Lerp; + base.OnOneTimeTearDown(); + } protected override bool m_EnableTimeTravel => true; protected override bool m_SetupIsACoroutine => false; @@ -102,7 +135,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 +145,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; diff --git a/pvpExceptions.json b/pvpExceptions.json index bda7ccbfe0..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", @@ -26,40 +18,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.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", 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(); - } } } } 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)