diff --git a/Assets/Mirror/Components/NetworkTransformBase.cs b/Assets/Mirror/Components/NetworkTransformBase.cs index afa27ed5c..6a49db1b6 100644 --- a/Assets/Mirror/Components/NetworkTransformBase.cs +++ b/Assets/Mirror/Components/NetworkTransformBase.cs @@ -29,7 +29,6 @@ public abstract class NetworkTransformBase : NetworkBehaviour [Tooltip("The Transform component to sync. May be on on this GameObject, or on a child.")] public Transform target; - // TODO SyncDirection { ClientToServer, ServerToClient } is easier? // Deprecated 2022-10-25 [Obsolete("NetworkTransform clientAuthority was replaced with syncDirection. To enable client authority, set SyncDirection to ClientToServer in the Inspector.")] [Header("[Obsolete]")] // Unity doesn't show obsolete warning for fields. do it manually. diff --git a/Assets/Mirror/Components/NetworkTransformReliable/NetworkTransformReliable.cs b/Assets/Mirror/Components/NetworkTransformReliable/NetworkTransformReliable.cs index 28d50c1ea..6c74a3310 100644 --- a/Assets/Mirror/Components/NetworkTransformReliable/NetworkTransformReliable.cs +++ b/Assets/Mirror/Components/NetworkTransformReliable/NetworkTransformReliable.cs @@ -1,6 +1,4 @@ // NetworkTransform V3 (reliable) by mischa (2022-10) -using System.Collections.Generic; -using System.Runtime.CompilerServices; using UnityEngine; namespace Mirror @@ -8,15 +6,7 @@ namespace Mirror [AddComponentMenu("Network/Network Transform (Reliable)")] public class NetworkTransformReliable : NetworkTransformBase { - [Header("Sync Only If Changed")] - [Tooltip("When true, changes are not sent unless greater than sensitivity values below.")] - public bool onlySyncOnChange = true; - [Tooltip("If we only sync on change, then we need to correct old snapshots if more time than sendInterval * multiplier has elapsed.\n\nOtherwise the first move will always start interpolating from the last move sequence's time, which will make it stutter when starting every time.")] - public float onlySyncOnChangeCorrectionMultiplier = 2; - [Header("Rotation")] - [Tooltip("Sensitivity of changes needed before an updated state is sent over the network")] - public float rotationSensitivity = 0.01f; [Tooltip("Apply smallest-three quaternion compression. This is lossy, you can disable it if the small rotation inaccuracies are noticeable in your project.")] public bool compressRotation = false; @@ -44,9 +34,6 @@ public class NetworkTransformReliable : NetworkTransformBase protected Vector3Long lastSerializedScale = Vector3Long.zero; protected Vector3Long lastDeserializedScale = Vector3Long.zero; - // Used to store last sent snapshots - protected TransformSnapshot last; - // update ////////////////////////////////////////////////////////////// // Update applies interpolation. void Update() @@ -68,8 +55,7 @@ void LateUpdate() // set dirty to trigger OnSerialize. either always, or only if changed. if (isServer || (IsClientWithAuthority && NetworkClient.ready)) // is NetworkClient.ready even needed? { - if (!onlySyncOnChange || Changed(Construct())) - SetDirty(); + SetDirty(); } } @@ -129,29 +115,6 @@ protected virtual void UpdateClient() } } - // check if position / rotation / scale changed since last sync - protected virtual bool Changed(TransformSnapshot current) => - // position is quantized and delta compressed. - // only consider it changed if the quantized representation is changed. - // careful: don't use 'serialized / deserialized last'. as it depends on sync mode etc. - QuantizedChanged(last.position, current.position, positionPrecision) || - // rotation isn't quantized / delta compressed. - // check with sensitivity. - Quaternion.Angle(last.rotation, current.rotation) > rotationSensitivity || - // scale is quantized and delta compressed. - // only consider it changed if the quantized representation is changed. - // careful: don't use 'serialized / deserialized last'. as it depends on sync mode etc. - QuantizedChanged(last.scale, current.scale, scalePrecision); - - // helper function to compare quantized representations of a Vector3 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected bool QuantizedChanged(Vector3 u, Vector3 v, float precision) - { - Compression.ScaleToLong(u, precision, out Vector3Long uQuantized); - Compression.ScaleToLong(v, precision, out Vector3Long vQuantized); - return uQuantized != vQuantized; - } - // NT may be used on client/server/host to Owner/Observers with // ServerToClient or ClientToServer. // however, OnSerialize should always delta against last. @@ -217,9 +180,6 @@ public override void OnSerialize(NetworkWriter writer, bool initialState) // save serialized as 'last' for next delta compression if (syncPosition) Compression.ScaleToLong(snapshot.position, positionPrecision, out lastSerializedPosition); if (syncScale) Compression.ScaleToLong(snapshot.scale, scalePrecision, out lastSerializedScale); - - // set 'last' - last = snapshot; } public override void OnDeserialize(NetworkReader reader, bool initialState) @@ -287,21 +247,6 @@ protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotat // protect against ever growing buffer size attacks if (serverSnapshots.Count >= connectionToClient.snapshotBufferSizeLimit) return; - // 'only sync on change' needs a correction on every new move sequence. - if (onlySyncOnChange && - NeedsCorrection(serverSnapshots, connectionToClient.remoteTimeStamp, NetworkServer.sendInterval, onlySyncOnChangeCorrectionMultiplier)) - { - RewriteHistory( - serverSnapshots, - connectionToClient.remoteTimeStamp, - NetworkTime.localTime, // arrival remote timestamp. NOT remote timeline. - NetworkServer.sendInterval, // Unity 2019 doesn't have timeAsDouble yet - target.localPosition, - target.localRotation, - target.localScale); - // Debug.Log($"{name}: corrected history on server to fix initial stutter after not sending for a while."); - } - // add a small timeline offset to account for decoupled arrival of // NetworkTime and NetworkTransform snapshots. // needs to be sendInterval. half sendInterval doesn't solve it. @@ -317,21 +262,6 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat // don't apply for local player with authority if (IsClientWithAuthority) return; - // 'only sync on change' needs a correction on every new move sequence. - if (onlySyncOnChange && - NeedsCorrection(clientSnapshots, NetworkClient.connection.remoteTimeStamp, NetworkClient.sendInterval, onlySyncOnChangeCorrectionMultiplier)) - { - RewriteHistory( - clientSnapshots, - NetworkClient.connection.remoteTimeStamp, // arrival remote timestamp. NOT remote timeline. - NetworkTime.localTime, // Unity 2019 doesn't have timeAsDouble yet - NetworkClient.sendInterval, - target.localPosition, - target.localRotation, - target.localScale); - // Debug.Log($"{name}: corrected history on client to fix initial stutter after not sending for a while."); - } - // add a small timeline offset to account for decoupled arrival of // NetworkTime and NetworkTransform snapshots. // needs to be sendInterval. half sendInterval doesn't solve it. @@ -342,47 +272,6 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat } // only sync on change ///////////////////////////////////////////////// - // snap interp. needs a continous flow of packets. - // 'only sync on change' interrupts it while not changed. - // once it restarts, snap interp. will interp from the last old position. - // this will cause very noticeable stutter for the first move each time. - // the fix is quite simple. - - // 1. detect if the remaining snapshot is too old from a past move. - static bool NeedsCorrection( - SortedList snapshots, - double remoteTimestamp, - double bufferTime, - double toleranceMultiplier) => - snapshots.Count == 1 && - remoteTimestamp - snapshots.Keys[0] >= bufferTime * toleranceMultiplier; - - // 2. insert a fake snapshot at current position, - // exactly one 'sendInterval' behind the newly received one. - static void RewriteHistory( - SortedList snapshots, - // timestamp of packet arrival, not interpolated remote time! - double remoteTimeStamp, - double localTime, - double sendInterval, - Vector3 position, - Quaternion rotation, - Vector3 scale) - { - // clear the previous snapshot - snapshots.Clear(); - - // insert a fake one at where we used to be, - // 'sendInterval' behind the new one. - SnapshotInterpolation.InsertIfNotExists(snapshots, new TransformSnapshot( - remoteTimeStamp - sendInterval, // arrival remote timestamp. NOT remote time. - localTime - sendInterval, // Unity 2019 doesn't have timeAsDouble yet - position, - rotation, - scale - )); - } - public override void Reset() { base.Reset(); @@ -393,9 +282,6 @@ public override void Reset() lastSerializedScale = Vector3Long.zero; lastDeserializedScale = Vector3Long.zero; - - // reset 'last' for delta too - last = new TransformSnapshot(0, 0, Vector3.zero, Quaternion.identity, Vector3.zero); } } } diff --git a/Assets/Mirror/Components/NetworkTransformUnreliable/NetworkTransform.cs b/Assets/Mirror/Components/NetworkTransformUnreliable/NetworkTransform.cs index 70a31afc2..838d845c2 100644 --- a/Assets/Mirror/Components/NetworkTransformUnreliable/NetworkTransform.cs +++ b/Assets/Mirror/Components/NetworkTransformUnreliable/NetworkTransform.cs @@ -1,6 +1,5 @@ // NetworkTransform V2 by mischa (2021-07) // comment out the below line to quickly revert the onlySyncOnChange feature -#define onlySyncOnChange_BANDWIDTH_SAVING using UnityEngine; namespace Mirror @@ -8,31 +7,6 @@ namespace Mirror [AddComponentMenu("Network/Network Transform (Unreliable)")] public class NetworkTransform : NetworkTransformBase { - // only sync when changed hack ///////////////////////////////////////// -#if onlySyncOnChange_BANDWIDTH_SAVING - [Header("Sync Only If Changed")] - [Tooltip("When true, changes are not sent unless greater than sensitivity values below.")] - public bool onlySyncOnChange = true; - - // 3 was original, but testing under really bad network conditions, 2%-5% packet loss and 250-1200ms ping, 5 proved to eliminate any twitching. - [Tooltip("How much time, as a multiple of send interval, has passed before clearing buffers.")] - public float bufferResetMultiplier = 5; - - [Header("Sensitivity"), Tooltip("Sensitivity of changes needed before an updated state is sent over the network")] - public float positionSensitivity = 0.01f; - public float rotationSensitivity = 0.01f; - public float scaleSensitivity = 0.01f; - - protected bool positionChanged; - protected bool rotationChanged; - protected bool scaleChanged; - - // Used to store last sent snapshots - protected TransformSnapshot lastSnapshot; - protected bool cachedSnapshotComparison; - protected bool hasSentUnchangedPosition; -#endif - double lastClientSendTime; double lastServerSendTime; @@ -84,39 +58,15 @@ void UpdateServerBroadcast() // send snapshot without timestamp. // receiver gets it from batch timestamp to save bandwidth. TransformSnapshot snapshot = Construct(); -#if onlySyncOnChange_BANDWIDTH_SAVING - cachedSnapshotComparison = CompareSnapshots(snapshot); - if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange) { return; } -#endif -#if onlySyncOnChange_BANDWIDTH_SAVING - RpcServerToClientSync( - // only sync what the user wants to sync - syncPosition && positionChanged ? snapshot.position : default(Vector3?), - syncRotation && rotationChanged ? snapshot.rotation : default(Quaternion?), - syncScale && scaleChanged ? snapshot.scale : default(Vector3?) - ); -#else RpcServerToClientSync( // only sync what the user wants to sync syncPosition ? snapshot.position : default(Vector3?), syncRotation ? snapshot.rotation : default(Quaternion?), syncScale ? snapshot.scale : default(Vector3?) ); -#endif lastServerSendTime = NetworkTime.localTime; -#if onlySyncOnChange_BANDWIDTH_SAVING - if (cachedSnapshotComparison) - { - hasSentUnchangedPosition = true; - } - else - { - hasSentUnchangedPosition = false; - lastSnapshot = snapshot; - } -#endif } } @@ -190,39 +140,15 @@ void UpdateClientBroadcast() // send snapshot without timestamp. // receiver gets it from batch timestamp to save bandwidth. TransformSnapshot snapshot = Construct(); -#if onlySyncOnChange_BANDWIDTH_SAVING - cachedSnapshotComparison = CompareSnapshots(snapshot); - if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange) { return; } -#endif -#if onlySyncOnChange_BANDWIDTH_SAVING - CmdClientToServerSync( - // only sync what the user wants to sync - syncPosition && positionChanged ? snapshot.position : default(Vector3?), - syncRotation && rotationChanged ? snapshot.rotation : default(Quaternion?), - syncScale && scaleChanged ? snapshot.scale : default(Vector3?) - ); -#else CmdClientToServerSync( // only sync what the user wants to sync syncPosition ? snapshot.position : default(Vector3?), syncRotation ? snapshot.rotation : default(Quaternion?), syncScale ? snapshot.scale : default(Vector3?) ); -#endif lastClientSendTime = NetworkTime.localTime; -#if onlySyncOnChange_BANDWIDTH_SAVING - if (cachedSnapshotComparison) - { - hasSentUnchangedPosition = true; - } - else - { - hasSentUnchangedPosition = false; - lastSnapshot = snapshot; - } -#endif } } @@ -286,17 +212,6 @@ public override void OnDeserialize(NetworkReader reader, bool initialState) } } -#if onlySyncOnChange_BANDWIDTH_SAVING - // Returns true if position, rotation AND scale are unchanged, within given sensitivity range. - protected virtual bool CompareSnapshots(TransformSnapshot currentSnapshot) - { - positionChanged = Vector3.SqrMagnitude(lastSnapshot.position - currentSnapshot.position) > positionSensitivity * positionSensitivity; - rotationChanged = Quaternion.Angle(lastSnapshot.rotation, currentSnapshot.rotation) > rotationSensitivity; - scaleChanged = Vector3.SqrMagnitude(lastSnapshot.scale - currentSnapshot.scale) > scaleSensitivity * scaleSensitivity; - - return (!positionChanged && !rotationChanged && !scaleChanged); - } -#endif // cmd ///////////////////////////////////////////////////////////////// // only unreliable. see comment above of this file. [Command(channel = Channels.Unreliable)] @@ -323,17 +238,6 @@ protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotat // only player owned objects (with a connection) can send to // server. we can get the timestamp from the connection. double timestamp = connectionToClient.remoteTimeStamp; -#if onlySyncOnChange_BANDWIDTH_SAVING - if (onlySyncOnChange) - { - double timeIntervalCheck = bufferResetMultiplier * NetworkClient.sendInterval; - - if (serverSnapshots.Count > 0 && serverSnapshots.Values[serverSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp) - { - Reset(); - } - } -#endif AddSnapshot(serverSnapshots, timestamp, position, rotation, scale); } @@ -362,17 +266,6 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat // but all of them go through NetworkClient.connection. // we can get the timestamp from there. double timestamp = NetworkClient.connection.remoteTimeStamp; -#if onlySyncOnChange_BANDWIDTH_SAVING - if (onlySyncOnChange) - { - double timeIntervalCheck = bufferResetMultiplier * NetworkServer.sendInterval; - - if (clientSnapshots.Count > 0 && clientSnapshots.Values[clientSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp) - { - Reset(); - } - } -#endif AddSnapshot(clientSnapshots, timestamp, position, rotation, scale); } }