fix: #3478 revert NetworkTransform sendMultiplier

This commit is contained in:
mischa 2023-05-07 23:04:01 +09:00
parent 0f41aa7a3e
commit 4367430b29
3 changed files with 75 additions and 113 deletions

View File

@ -57,31 +57,6 @@ public abstract class NetworkTransformBase : NetworkBehaviour
[Tooltip("Set to false to remove scale smoothing. Example use-case: Instant flipping of sprites that use -X and +X for direction.")]
public bool interpolateScale = true;
[Header("Send Interval Multiplier")]
[Tooltip("Check/Sync every multiple of Network Manager send interval (= 1 / NM Send Rate), instead of every send interval.")]
[Range(1, 120)]
public uint sendIntervalMultiplier = 1; // not implemented yet
[Header("Timeline Offset")]
[Tooltip("Add a small timeline offset to account for decoupled arrival of NetworkTime and NetworkTransform snapshots.\nfixes: https://github.com/MirrorNetworking/Mirror/issues/3427")]
public bool timelineOffset = false;
// Ninja's Notes on offset & mulitplier:
//
// In a no multiplier scenario:
// 1. Snapshots are sent every frame (frame being 1 NM send interval).
// 2. Time Interpolation is set to be 'behind' by 2 frames times.
// In theory where everything works, we probably have around 2 snapshots before we need to interpolate snapshots. From NT perspective, we should always have around 2 snapshots ready, so no stutter.
//
// In a multiplier scenario:
// 1. Snapshots are sent every 10 frames.
// 2. Time Interpolation remains 'behind by 2 frames'.
// When everything works, we are receiving NT snapshots every 10 frames, but start interpolating after 2.
// Even if I assume we had 2 snapshots to begin with to start interpolating (which we don't), by the time we reach 13th frame, we are out of snapshots, and have to wait 7 frames for next snapshot to come. This is the reason why we absolutely need the timestamp adjustment. We are starting way too early to interpolate.
//
protected double timeStampAdjustment => NetworkServer.sendInterval * (sendIntervalMultiplier - 1);
protected double offset => timelineOffset ? NetworkServer.sendInterval * sendIntervalMultiplier : 0;
// debugging ///////////////////////////////////////////////////////////
[Header("Debug")]
public bool showGizmos;
@ -124,8 +99,13 @@ protected virtual TransformSnapshot Construct()
// NetworkTime.localTime for double precision until Unity has it too
return new TransformSnapshot(
// our local time is what the other end uses as remote time
#if !UNITY_2020_3_OR_NEWER
NetworkTime.localTime, // Unity 2019 doesn't have timeAsDouble yet
0, // the other end fills out local time itself
#else
Time.timeAsDouble,
#endif
// the other end fills out local time itself
0,
target.localPosition,
target.localRotation,
target.localScale
@ -150,7 +130,11 @@ protected void AddSnapshot(SortedList<double, TransformSnapshot> snapshots, doub
// insert transform snapshot
SnapshotInterpolation.InsertIfNotExists(snapshots, new TransformSnapshot(
timeStamp, // arrival remote timestamp. NOT remote time.
#if !UNITY_2020_3_OR_NEWER
NetworkTime.localTime, // Unity 2019 doesn't have timeAsDouble yet
#else
Time.timeAsDouble,
#endif
position.Value,
rotation.Value,
scale.Value

View File

@ -11,10 +11,6 @@ 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;
uint sendIntervalCounter = 0;
double lastSendIntervalTime = double.MinValue;
[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;
@ -37,6 +33,10 @@ public class NetworkTransformReliable : NetworkTransformBase
[Range(0.00_01f, 1f)] // disallow 0 division. 1mm to 1m precision is enough range.
public float scalePrecision = 0.01f; // 1 cm
[Header("Snapshot Interpolation")]
[Tooltip("Add a small timeline offset to account for decoupled arrival of NetworkTime and NetworkTransform snapshots.\nfixes: https://github.com/MirrorNetworking/Mirror/issues/3427")]
public bool timelineOffset = false;
// delta compression needs to remember 'last' to compress against
protected Vector3Long lastSerializedPosition = Vector3Long.zero;
protected Vector3Long lastDeserializedPosition = Vector3Long.zero;
@ -47,9 +47,8 @@ public class NetworkTransformReliable : NetworkTransformBase
// Used to store last sent snapshots
protected TransformSnapshot last;
protected int lastClientCount = 1;
// update //////////////////////////////////////////////////////////////
// Update applies interpolation.
void Update()
{
// if server then always sync to others.
@ -59,19 +58,18 @@ void Update()
else if (isClient) UpdateClient();
}
// LateUpdate sets dirty.
// movement scripts may change positions in Update.
// use LateUpdate to ensure changes are detected in the same frame.
// otherwise this may run before user update, delaying detection until next frame.
// this would cause visible jitter.
void LateUpdate()
{
// set dirty to trigger OnSerialize. either always, or only if changed.
// It has to be checked in LateUpdate() for onlySyncOnChange to avoid
// the possibility of Update() running first before the object's movement
// script's Update(), which then causes NT to send every alternate frame
// instead.
if (isServer || (IsClientWithAuthority && NetworkClient.ready))
if (isServer || (IsClientWithAuthority && NetworkClient.ready)) // is NetworkClient.ready even needed?
{
if (sendIntervalCounter == sendIntervalMultiplier && (!onlySyncOnChange || Changed(Construct())))
if (!onlySyncOnChange || Changed(Construct()))
SetDirty();
CheckLastSendTime();
}
}
@ -128,19 +126,6 @@ protected virtual void UpdateClient()
TransformSnapshot computed = TransformSnapshot.Interpolate(from, to, t);
Apply(computed, to);
}
lastClientCount = clientSnapshots.Count;
}
}
protected virtual void CheckLastSendTime()
{
// timeAsDouble not available in older Unity versions.
if (AccurateInterval.Elapsed(NetworkTime.localTime, NetworkServer.sendInterval, ref lastSendIntervalTime))
{
if (sendIntervalCounter == sendIntervalMultiplier)
sendIntervalCounter = 0;
sendIntervalCounter++;
}
}
@ -188,16 +173,6 @@ public override void OnSerialize(NetworkWriter writer, bool initialState)
// initial
if (initialState)
{
// If there is a last serialized snapshot, we use it.
// This prevents the new client getting a snapshot that is different
// from what the older clients last got. If this happens, and on the next
// regular serialisation the delta compression will get wrong values.
// Notes:
// 1. Interestingly only the older clients have it wrong, because at the end
// of this function, last = snapshot which is the initial state's snapshot
// 2. Regular NTR gets by this bug because it sends every frame anyway so initialstate
// snapshot constructed would have been the same as the last anyway.
if (last.remoteTime > 0) snapshot = last;
if (syncPosition) writer.WriteVector3(snapshot.position);
if (syncRotation)
{
@ -234,6 +209,9 @@ public override void OnSerialize(NetworkWriter writer, bool initialState)
Compression.ScaleToLong(snapshot.scale, scalePrecision, out Vector3Long quantized);
DeltaCompression.Compress(writer, lastSerializedScale, quantized);
}
// int written = writer.Position - before;
// Debug.Log($"{name} compressed to {written} bytes");
}
// save serialized as 'last' for next delta compression
@ -311,16 +289,17 @@ protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotat
// 'only sync on change' needs a correction on every new move sequence.
if (onlySyncOnChange &&
NeedsCorrection(serverSnapshots, connectionToClient.remoteTimeStamp, NetworkServer.sendInterval * sendIntervalMultiplier, onlySyncOnChangeCorrectionMultiplier))
NeedsCorrection(serverSnapshots, connectionToClient.remoteTimeStamp, NetworkServer.sendInterval, onlySyncOnChangeCorrectionMultiplier))
{
RewriteHistory(
serverSnapshots,
connectionToClient.remoteTimeStamp,
NetworkTime.localTime, // arrival remote timestamp. NOT remote timeline.
NetworkServer.sendInterval * sendIntervalMultiplier, // Unity 2019 doesn't have timeAsDouble yet
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
@ -328,7 +307,8 @@ protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotat
// needs to be sendInterval. half sendInterval doesn't solve it.
// https://github.com/MirrorNetworking/Mirror/issues/3427
// remove this after LocalWorldState.
AddSnapshot(serverSnapshots, connectionToClient.remoteTimeStamp + timeStampAdjustment + offset, position, rotation, scale);
double offset = timelineOffset ? NetworkServer.sendInterval : 0;
AddSnapshot(serverSnapshots, connectionToClient.remoteTimeStamp + offset, position, rotation, scale);
}
// server broadcasts sync message to all clients
@ -339,16 +319,17 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat
// 'only sync on change' needs a correction on every new move sequence.
if (onlySyncOnChange &&
NeedsCorrection(clientSnapshots, NetworkClient.connection.remoteTimeStamp, NetworkClient.sendInterval * sendIntervalMultiplier, onlySyncOnChangeCorrectionMultiplier))
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 * sendIntervalMultiplier,
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
@ -356,7 +337,8 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat
// needs to be sendInterval. half sendInterval doesn't solve it.
// https://github.com/MirrorNetworking/Mirror/issues/3427
// remove this after LocalWorldState.
AddSnapshot(clientSnapshots, NetworkClient.connection.remoteTimeStamp + timeStampAdjustment + offset, position, rotation, scale);
double offset = timelineOffset ? NetworkServer.sendInterval : 0;
AddSnapshot(clientSnapshots, NetworkClient.connection.remoteTimeStamp + offset, position, rotation, scale);
}
// only sync on change /////////////////////////////////////////////////

View File

@ -14,9 +14,6 @@ public class NetworkTransform : NetworkTransformBase
[Tooltip("When true, changes are not sent unless greater than sensitivity values below.")]
public bool onlySyncOnChange = true;
uint sendIntervalCounter = 0;
double lastSendIntervalTime = double.MinValue;
// 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;
@ -36,41 +33,17 @@ public class NetworkTransform : NetworkTransformBase
protected bool hasSentUnchangedPosition;
#endif
double lastClientSendTime;
double lastServerSendTime;
// update //////////////////////////////////////////////////////////////
// Update applies interpolation
void Update()
{
if (isServer) UpdateServerInterpolation();
// for all other clients (and for local player if !authority),
// we need to apply snapshots from the buffer.
// 'else if' because host mode shouldn't interpolate client
else if (isClient && !IsClientWithAuthority) UpdateClientInterpolation();
}
// LateUpdate broadcasts.
// movement scripts may change positions in Update.
// use LateUpdate to ensure changes are detected in the same frame.
// otherwise this may run before user update, delaying detection until next frame.
// this could cause visible jitter.
void LateUpdate()
{
// if server then always sync to others.
if (isServer) UpdateServerBroadcast();
// client authority, and local player (= allowed to move myself)?
if (isServer) UpdateServer();
// 'else if' because host mode shouldn't send anything to server.
// it is the server. don't overwrite anything there.
else if (isClient && IsClientWithAuthority) UpdateClientBroadcast();
}
protected virtual void CheckLastSendTime()
{
// timeAsDouble not available in older Unity versions.
if (AccurateInterval.Elapsed(NetworkTime.localTime, NetworkServer.sendInterval, ref lastSendIntervalTime))
{
if (sendIntervalCounter == sendIntervalMultiplier)
sendIntervalCounter = 0;
sendIntervalCounter++;
}
else if (isClient) UpdateClient();
}
void UpdateServerBroadcast()
@ -105,9 +78,7 @@ void UpdateServerBroadcast()
// authoritative movement done by the host will have to be broadcasted
// here by checking IsClientWithAuthority.
// TODO send same time that NetworkServer sends time snapshot?
CheckLastSendTime();
if (sendIntervalCounter == sendIntervalMultiplier && // same interval as time interpolation!
if (NetworkTime.localTime >= lastServerSendTime + NetworkServer.sendInterval && // same interval as time interpolation!
(syncDirection == SyncDirection.ServerToClient || IsClientWithAuthority))
{
// send snapshot without timestamp.
@ -134,6 +105,7 @@ void UpdateServerBroadcast()
);
#endif
lastServerSendTime = NetworkTime.localTime;
#if onlySyncOnChange_BANDWIDTH_SAVING
if (cachedSnapshotComparison)
{
@ -179,6 +151,15 @@ void UpdateServerInterpolation()
}
}
void UpdateServer()
{
// broadcast to all clients each 'sendInterval'
UpdateServerBroadcast();
// apply buffered snapshots IF client authority
UpdateServerInterpolation();
}
void UpdateClientBroadcast()
{
// https://github.com/vis2k/Mirror/pull/2992/
@ -204,8 +185,7 @@ void UpdateClientBroadcast()
// DO NOT send nulls if not changed 'since last send' either. we
// send unreliable and don't know which 'last send' the other end
// received successfully.
CheckLastSendTime();
if (sendIntervalCounter == sendIntervalMultiplier) // same interval as time interpolation!
if (NetworkTime.localTime >= lastClientSendTime + NetworkClient.sendInterval) // same interval as time interpolation!
{
// send snapshot without timestamp.
// receiver gets it from batch timestamp to save bandwidth.
@ -231,6 +211,7 @@ void UpdateClientBroadcast()
);
#endif
lastClientSendTime = NetworkTime.localTime;
#if onlySyncOnChange_BANDWIDTH_SAVING
if (cachedSnapshotComparison)
{
@ -264,6 +245,21 @@ void UpdateClientInterpolation()
Apply(computed, to);
}
void UpdateClient()
{
// client authority, and local player (= allowed to move myself)?
if (IsClientWithAuthority)
{
UpdateClientBroadcast();
}
// for all other clients (and for local player if !authority),
// we need to apply snapshots from the buffer
else
{
UpdateClientInterpolation();
}
}
public override void OnSerialize(NetworkWriter writer, bool initialState)
{
// sync target component's position on spawn.
@ -330,7 +326,7 @@ protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotat
#if onlySyncOnChange_BANDWIDTH_SAVING
if (onlySyncOnChange)
{
double timeIntervalCheck = bufferResetMultiplier * sendIntervalMultiplier * NetworkClient.sendInterval;
double timeIntervalCheck = bufferResetMultiplier * NetworkClient.sendInterval;
if (serverSnapshots.Count > 0 && serverSnapshots.Values[serverSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp)
{
@ -338,7 +334,7 @@ protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotat
}
}
#endif
AddSnapshot(serverSnapshots, connectionToClient.remoteTimeStamp + timeStampAdjustment + offset, position, rotation, scale);
AddSnapshot(serverSnapshots, timestamp, position, rotation, scale);
}
// rpc /////////////////////////////////////////////////////////////////
@ -369,7 +365,7 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat
#if onlySyncOnChange_BANDWIDTH_SAVING
if (onlySyncOnChange)
{
double timeIntervalCheck = bufferResetMultiplier * sendIntervalMultiplier * NetworkServer.sendInterval;
double timeIntervalCheck = bufferResetMultiplier * NetworkServer.sendInterval;
if (clientSnapshots.Count > 0 && clientSnapshots.Values[clientSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp)
{
@ -377,7 +373,7 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat
}
}
#endif
AddSnapshot(clientSnapshots, NetworkClient.connection.remoteTimeStamp + timeStampAdjustment + offset, position, rotation, scale);
AddSnapshot(clientSnapshots, timestamp, position, rotation, scale);
}
}
}