diff --git a/Assets/Mirror/Components/NetworkTransform/NetworkTransformUnreliable.cs b/Assets/Mirror/Components/NetworkTransform/NetworkTransformUnreliable.cs index ab3b2d7b7..235a65a1d 100644 --- a/Assets/Mirror/Components/NetworkTransform/NetworkTransformUnreliable.cs +++ b/Assets/Mirror/Components/NetworkTransform/NetworkTransformUnreliable.cs @@ -14,8 +14,6 @@ public class NetworkTransformUnreliable : NetworkTransformBase // Testing under really bad network conditions, 2%-5% packet loss and 250-1200ms ping, 5 proved to eliminate any twitching, however this should not be the default as it is a rare case Developers may want to cover. [Tooltip("How much time, as a multiple of send interval, has passed before clearing buffers.\nA larger buffer means more delay, but results in smoother movement.\nExample: 1 for faster responses minimal smoothing, 5 covers bad pings but has noticable delay, 3 is recommended for balanced results,.")] public float bufferResetMultiplier = 3; - [Tooltip("Detect and send only changed data, such as Position X and Z, not the full Vector3 of X Y Z. Lowers network data at cost of extra calculations.")] - public bool changedDetection = true; [Header("Sensitivity"), Tooltip("Sensitivity of changes needed before an updated state is sent over the network")] public float positionSensitivity = 0.01f; @@ -117,67 +115,22 @@ void UpdateServerBroadcast() // receiver gets it from batch timestamp to save bandwidth. TransformSnapshot snapshot = Construct(); - if (changedDetection) + cachedChangedComparison = CompareChangedSnapshots(snapshot); + + if ((cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) && hasSentUnchangedPosition && onlySyncOnChange) { return; } + + SyncData syncData = new SyncData(cachedChangedComparison, snapshot); + + RpcServerToClientSync(syncData); + + if (cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) { - cachedChangedComparison = CompareChangedSnapshots(snapshot); - - if ((cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) && hasSentUnchangedPosition && onlySyncOnChange) { return; } - - SyncData syncData = new SyncData(cachedChangedComparison, snapshot); - - RpcServerToClientSync(syncData); - - if (cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) - { - hasSentUnchangedPosition = true; - } - else - { - hasSentUnchangedPosition = false; - UpdateLastSentSnapshot(cachedChangedComparison, snapshot); - } + hasSentUnchangedPosition = true; } else { - cachedSnapshotComparison = CompareSnapshots(snapshot); - if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange) { return; } - - if (compressRotation) - { - RpcServerToClientSyncCompressRotation( - // only sync what the user wants to sync - syncPosition && positionChanged ? snapshot.position : default(Vector3?), - syncRotation && rotationChanged ? Compression.CompressQuaternion(snapshot.rotation) : default(uint?), - syncScale && scaleChanged ? snapshot.scale : default(Vector3?) - ); - } - else - { - 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?) - ); - } - - if (cachedSnapshotComparison) - { - hasSentUnchangedPosition = true; - } - else - { - hasSentUnchangedPosition = false; - - // Fixes https://github.com/MirrorNetworking/Mirror/issues/3572 - // This also fixes https://github.com/MirrorNetworking/Mirror/issues/3573 - // with the exception of Quaternion.Angle sensitivity has to be > 0.16. - // Unity issue, we are leaving it as is. - - if (positionChanged) lastSnapshot.position = snapshot.position; - if (rotationChanged) lastSnapshot.rotation = snapshot.rotation; - if (positionChanged) lastSnapshot.scale = snapshot.scale; - } + hasSentUnchangedPosition = false; + UpdateLastSentSnapshot(cachedChangedComparison, snapshot); } } } @@ -245,66 +198,22 @@ void UpdateClientBroadcast() // receiver gets it from batch timestamp to save bandwidth. TransformSnapshot snapshot = Construct(); - if (changedDetection) + cachedChangedComparison = CompareChangedSnapshots(snapshot); + + if ((cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) && hasSentUnchangedPosition && onlySyncOnChange) { return; } + + SyncData syncData = new SyncData(cachedChangedComparison, snapshot); + + CmdClientToServerSync(syncData); + + if (cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) { - cachedChangedComparison = CompareChangedSnapshots(snapshot); - - if ((cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) && hasSentUnchangedPosition && onlySyncOnChange) { return; } - - SyncData syncData = new SyncData(cachedChangedComparison, snapshot); - - CmdClientToServerSync(syncData); - - if (cachedChangedComparison == Changed.None || cachedChangedComparison == Changed.CompressRot) - { - hasSentUnchangedPosition = true; - } - else - { - hasSentUnchangedPosition = false; - UpdateLastSentSnapshot(cachedChangedComparison, snapshot); - } + hasSentUnchangedPosition = true; } else { - cachedSnapshotComparison = CompareSnapshots(snapshot); - if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange) { return; } - - if (compressRotation) - { - CmdClientToServerSyncCompressRotation( - // only sync what the user wants to sync - syncPosition && positionChanged ? snapshot.position : default(Vector3?), - syncRotation && rotationChanged ? Compression.CompressQuaternion(snapshot.rotation) : default(uint?), - syncScale && scaleChanged ? snapshot.scale : default(Vector3?) - ); - } - else - { - 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?) - ); - } - - if (cachedSnapshotComparison) - { - hasSentUnchangedPosition = true; - } - else - { - hasSentUnchangedPosition = false; - - // Fixes https://github.com/MirrorNetworking/Mirror/issues/3572 - // This also fixes https://github.com/MirrorNetworking/Mirror/issues/3573 - // with the exception of Quaternion.Angle sensitivity has to be > 0.16. - // Unity issue, we are leaving it as is. - if (positionChanged) lastSnapshot.position = snapshot.position; - if (rotationChanged) lastSnapshot.rotation = snapshot.rotation; - if (positionChanged) lastSnapshot.scale = snapshot.scale; - } + hasSentUnchangedPosition = false; + UpdateLastSentSnapshot(cachedChangedComparison, snapshot); } } } @@ -364,119 +273,6 @@ protected virtual bool CompareSnapshots(TransformSnapshot currentSnapshot) return (!positionChanged && !rotationChanged && !scaleChanged); } - // cmd ///////////////////////////////////////////////////////////////// - // only unreliable. see comment above of this file. - [Command(channel = Channels.Unreliable)] - void CmdClientToServerSync(Vector3? position, Quaternion? rotation, Vector3? scale) - { - OnClientToServerSync(position, rotation, scale); - //For client authority, immediately pass on the client snapshot to all other - //clients instead of waiting for server to send its snapshots. - if (syncDirection == SyncDirection.ClientToServer) - RpcServerToClientSync(position, rotation, scale); - } - - // cmd ///////////////////////////////////////////////////////////////// - // only unreliable. see comment above of this file. - [Command(channel = Channels.Unreliable)] - void CmdClientToServerSyncCompressRotation(Vector3? position, uint? rotation, Vector3? scale) - { - // A fix to not apply current interpolated GetRotation when receiving null/unchanged value, instead use last sent snapshot rotation. - Quaternion newRotation; - if (rotation.HasValue) - { - newRotation = Compression.DecompressQuaternion((uint)rotation); - } - else - { - newRotation = serverSnapshots.Count > 0 ? serverSnapshots.Values[serverSnapshots.Count - 1].rotation : GetRotation(); - } - OnClientToServerSync(position, newRotation, scale); - //For client authority, immediately pass on the client snapshot to all other - //clients instead of waiting for server to send its snapshots. - if (syncDirection == SyncDirection.ClientToServer) - RpcServerToClientSyncCompressRotation(position, rotation, scale); - } - - // local authority client sends sync message to server for broadcasting - protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotation, Vector3? scale) - { - // only apply if in client authority mode - if (syncDirection != SyncDirection.ClientToServer) return; - - // protect against ever growing buffer size attacks - if (serverSnapshots.Count >= connectionToClient.snapshotBufferSizeLimit) return; - - // 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) - { - double timeIntervalCheck = bufferResetMultiplier * sendIntervalMultiplier * NetworkClient.sendInterval; - - if (serverSnapshots.Count > 0 && serverSnapshots.Values[serverSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp) - ResetState(); - } - - AddSnapshot(serverSnapshots, connectionToClient.remoteTimeStamp + timeStampAdjustment + offset, position, rotation, scale); - } - - // rpc ///////////////////////////////////////////////////////////////// - // only unreliable. see comment above of this file. - [ClientRpc(channel = Channels.Unreliable)] - void RpcServerToClientSync(Vector3? position, Quaternion? rotation, Vector3? scale) => - OnServerToClientSync(position, rotation, scale); - - // rpc ///////////////////////////////////////////////////////////////// - // only unreliable. see comment above of this file. - [ClientRpc(channel = Channels.Unreliable)] - void RpcServerToClientSyncCompressRotation(Vector3? position, uint? rotation, Vector3? scale) - { - // A fix to not apply current interpolated GetRotation when receiving null/unchanged value, instead use last sent snapshot rotation. - Quaternion newRotation; - if (rotation.HasValue) - { - newRotation = Compression.DecompressQuaternion((uint)rotation); - } - else - { - newRotation = clientSnapshots.Count > 0 ? clientSnapshots.Values[clientSnapshots.Count - 1].rotation : GetRotation(); - } - OnServerToClientSync(position, newRotation, scale); - } - - // server broadcasts sync message to all clients - protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotation, Vector3? scale) - { - // in host mode, the server sends rpcs to all clients. - // the host client itself will receive them too. - // -> host server is always the source of truth - // -> we can ignore any rpc on the host client - // => otherwise host objects would have ever growing clientBuffers - // (rpc goes to clients. if isServer is true too then we are host) - if (isServer) return; - - // don't apply for local player with authority - if (IsClientWithAuthority) return; - - // on the client, we receive rpcs for all entities. - // not all of them have a connectionToServer. - // but all of them go through NetworkClient.connection. - // we can get the timestamp from there. - double timestamp = NetworkClient.connection.remoteTimeStamp; - - if (onlySyncOnChange) - { - double timeIntervalCheck = bufferResetMultiplier * sendIntervalMultiplier * NetworkServer.sendInterval; - - if (clientSnapshots.Count > 0 && clientSnapshots.Values[clientSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp) - ResetState(); - } - - AddSnapshot(clientSnapshots, NetworkClient.connection.remoteTimeStamp + timeStampAdjustment + offset, position, rotation, scale); - } - protected virtual void UpdateLastSentSnapshot(Changed change, TransformSnapshot currentSnapshot) { if (change == Changed.None || change == Changed.CompressRot) return; diff --git a/Assets/Mirror/Tests/Editor/NetworkTransform/NetworkTransform2kTests.cs b/Assets/Mirror/Tests/Editor/NetworkTransform/NetworkTransform2kTests.cs index 8ea890997..c64e583b8 100644 --- a/Assets/Mirror/Tests/Editor/NetworkTransform/NetworkTransform2kTests.cs +++ b/Assets/Mirror/Tests/Editor/NetworkTransform/NetworkTransform2kTests.cs @@ -12,10 +12,10 @@ public class NetworkTransformExposed : NetworkTransformUnreliable public new TransformSnapshot Construct() => base.Construct(); public void Apply(TransformSnapshot interpolated) => base.Apply(interpolated, interpolated); - public new void OnClientToServerSync(Vector3? position, Quaternion? rotation, Vector3? scale) => - base.OnClientToServerSync(position, rotation, scale); - public new void OnServerToClientSync(Vector3? position, Quaternion? rotation, Vector3? scale) => - base.OnServerToClientSync(position, rotation, scale); + public new void OnClientToServerSync(SyncData syncData) => + base.OnClientToServerSync(syncData); + public new void OnServerToClientSync(SyncData syncData) => + base.OnServerToClientSync(syncData); } public class NetworkTransform2kTests : MirrorTest @@ -199,7 +199,8 @@ public void OnClientToServerSync_WithoutClientAuthority() { // call OnClientToServerSync without authority component.syncDirection = SyncDirection.ServerToClient; - component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); + SyncData syncData = new SyncData((Changed)255, Vector3.zero, Quaternion.identity, Vector3.zero); + component.OnClientToServerSync(syncData); Assert.That(component.serverSnapshots.Count, Is.EqualTo(0)); } @@ -208,7 +209,8 @@ public void OnClientToServerSync_WithClientAuthority() { // call OnClientToServerSync with authority component.syncDirection = SyncDirection.ClientToServer; - component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); + SyncData syncData = new SyncData((Changed)255, Vector3.zero, Quaternion.identity, Vector3.zero); + component.OnClientToServerSync(syncData); Assert.That(component.serverSnapshots.Count, Is.EqualTo(1)); } @@ -219,13 +221,12 @@ public void OnClientToServerSync_WithClientAuthority_BufferSizeLimit() // authority is required component.syncDirection = SyncDirection.ClientToServer; - + SyncData syncData = new SyncData((Changed)255, Vector3.zero, Quaternion.identity, Vector3.zero); // add first should work - component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); + component.OnClientToServerSync(syncData); Assert.That(component.serverSnapshots.Count, Is.EqualTo(1)); - // add second should be too much - component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); + component.OnClientToServerSync(syncData); Assert.That(component.serverSnapshots.Count, Is.EqualTo(1)); } @@ -240,7 +241,8 @@ public void OnClientToServerSync_WithClientAuthority_Nullables_Uses_Last() // call OnClientToServerSync with authority and nullable types // to make sure it uses the last valid position then. component.syncDirection = SyncDirection.ClientToServer; - component.OnClientToServerSync(new Vector3?(), new Quaternion?(), new Vector3?()); + SyncData syncData = new SyncData((Changed)255, Vector3.zero, Quaternion.identity, Vector3.zero); + component.OnClientToServerSync(syncData); Assert.That(component.serverSnapshots.Count, Is.EqualTo(1)); TransformSnapshot first = component.serverSnapshots.Values[0]; Assert.That(first.position, Is.EqualTo(Vector3.left)); @@ -257,9 +259,11 @@ public void OnServerToClientSync_WithoutClientAuthority() component.netIdentity.isClient = true; component.netIdentity.isLocalPlayer = true; + SyncData syncData = new SyncData((Changed)255, Vector3.zero, Quaternion.identity, Vector3.zero); + // call OnServerToClientSync without authority component.syncDirection = SyncDirection.ServerToClient; - component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); + component.OnServerToClientSync(syncData); Assert.That(component.clientSnapshots.Count, Is.EqualTo(1)); } @@ -277,12 +281,14 @@ public void OnServerToClientSync_WithoutClientAuthority_bufferSizeLimit() // client authority has to be disabled component.syncDirection = SyncDirection.ServerToClient; + SyncData syncData = new SyncData((Changed)255, Vector3.zero, Quaternion.identity, Vector3.zero); + // add first should work - component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); + component.OnServerToClientSync(syncData); Assert.That(component.clientSnapshots.Count, Is.EqualTo(1)); // add second should be too much - component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); + component.OnServerToClientSync(syncData); Assert.That(component.clientSnapshots.Count, Is.EqualTo(1)); } @@ -297,7 +303,8 @@ public void OnServerToClientSync_WithClientAuthority() // call OnServerToClientSync with authority component.syncDirection = SyncDirection.ClientToServer; - component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); + SyncData syncData = new SyncData((Changed)255, Vector3.zero, Quaternion.identity, Vector3.zero); + component.OnServerToClientSync(syncData); Assert.That(component.clientSnapshots.Count, Is.EqualTo(0)); } @@ -319,7 +326,8 @@ public void OnServerToClientSync_WithClientAuthority_Nullables_Uses_Last() // call OnClientToServerSync with authority and nullable types // to make sure it uses the last valid position then. - component.OnServerToClientSync(new Vector3?(), new Quaternion?(), new Vector3?()); + SyncData syncData = new SyncData((Changed)255, Vector3.zero, Quaternion.identity, Vector3.zero); + component.OnServerToClientSync(syncData); Assert.That(component.clientSnapshots.Count, Is.EqualTo(1)); TransformSnapshot first = component.clientSnapshots.Values[0]; Assert.That(first.position, Is.EqualTo(Vector3.left));