From 114cbf0498956adde8aeb49d3667324f34dfe9cc Mon Sep 17 00:00:00 2001 From: mischa Date: Wed, 16 Oct 2024 16:35:50 +0200 Subject: [PATCH] delta: only sync on change --- .../NetworkTransformHybrid2022.cs | 67 +++++++++++++++---- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid2022.cs b/Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid2022.cs index b268f78f4..39743b71e 100644 --- a/Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid2022.cs +++ b/Assets/Mirror/Components/NetworkTransform/NetworkTransformHybrid2022.cs @@ -22,6 +22,7 @@ // buffer for bufferTime but end up closer to the original time using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using UnityEngine; namespace Mirror @@ -83,13 +84,8 @@ public class NetworkTransformHybrid2022 : NetworkBehaviour [Tooltip("When true, changes are not sent unless greater than sensitivity values below.")] public bool onlySyncOnChange = true; - [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; - // Used to store last sent snapshots - protected TransformSnapshot lastSnapshot; + protected TransformSnapshot last; [Header("Compression")] [Tooltip("Position is rounded in order to drastically minimize bandwidth.\n\nFor example, a precision of 0.01 rounds to a centimeter. In other words, sub-centimeter movements aren't synced until they eventually exceeded an actual centimeter.\n\nDepending on how important the object is, a precision of 0.01-0.10 (1-10 cm) is recommended.\n\nFor example, even a 1cm precision combined with delta compression cuts the Benchmark demo's bandwidth in half, compared to sending every tiny change.")] @@ -184,6 +180,29 @@ protected virtual void ApplySnapshot(TransformSnapshot interpolated) if (syncScale) target.localScale = interpolated.scale; } + // check if position / rotation / scale changed since last _full reliable_ 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) > rotationPrecision || + // 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; + } + // serialization /////////////////////////////////////////////////////// // serialize server->client baseline into a NetworkWriter. // for use in RpcSync and OnSerialize for spawn message. @@ -219,6 +238,9 @@ void SerializeServerBaseline(NetworkWriter writer, Vector3 position, Quaternion // included in deltas to ensure they are on top of the correct baseline lastSerializedBaselineTick = (byte)Time.frameCount; lastServerBaselineTime = NetworkTime.localTime; + + // set 'last' + last = new TransformSnapshot(0, 0, position, rotation, scale); } void DeserializeServerBaseline(NetworkReader reader) @@ -452,6 +474,7 @@ protected virtual void OnServerToClientDeltaSync(byte baselineTick, Vector3? pos } // update ////////////////////////////////////////////////////////////// + bool baselineDirty = true; void UpdateServerBaseline() { // send a reliable baseline every 1 Hz @@ -460,13 +483,24 @@ void UpdateServerBaseline() // only send a new reliable baseline if changed since last time TransformSnapshot snapshot = ConstructSnapshot(); - // send snapshot without timestamp. - // receiver gets it from batch timestamp to save bandwidth. - using (NetworkWriterPooled writer = NetworkWriterPool.Get()) + // check if changed (unless that feature is disabled). + // baseline is guaranteed to be delivered over reliable. + // here is the only place where we can check for changes. + if (!onlySyncOnChange || Changed(snapshot)) { - SerializeServerBaseline(writer, snapshot.position, snapshot.rotation, snapshot.scale); - RpcServerToClientBaselineSync(writer); + // reliable just changed. keep sending deltas until it's unchanged again. + baselineDirty = true; + + // send snapshot without timestamp. + // receiver gets it from batch timestamp to save bandwidth. + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) + { + SerializeServerBaseline(writer, snapshot.position, snapshot.rotation, snapshot.scale); + RpcServerToClientBaselineSync(writer); + } } + // indicate that we should stop sending deltas now + else baselineDirty = false; } } @@ -502,6 +536,12 @@ void UpdateServerDelta() // authoritative movement done by the host will have to be broadcasted // here by checking IsClientWithAuthority. // TODO send same time that NetworkServer sends time snapshot? + + // only sync on change: + // unreliable isn't guaranteed to be delivered so this depends on reliable baseline. + // if baseline is dirty, send unreliables every sendInterval until baseline is not dirty anymore. + if (onlySyncOnChange && !baselineDirty) return; + if (NetworkTime.localTime >= lastServerSendTime + sendInterval) // CUSTOM CHANGE: allow custom sendRate + sendInterval again { // send snapshot without timestamp. @@ -523,7 +563,6 @@ void UpdateServerDelta() } lastServerSendTime = NetworkTime.localTime; - lastSnapshot = snapshot; } } @@ -614,7 +653,6 @@ void UpdateClient() ); lastClientSendTime = NetworkTime.localTime; - lastSnapshot = snapshot; } } // for all other clients (and for local player if !authority), @@ -789,6 +827,9 @@ public virtual void Reset() lastSerializedScale = Vector3Long.zero; lastDeserializedScale = Vector3Long.zero; + // reset 'last' for delta too + last = new TransformSnapshot(0, 0, Vector3.zero, Quaternion.identity, Vector3.zero); + Debug.Log($"[{name}] Reset to baselineTick=0"); }