mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 11:00:32 +00:00
breaking: expose NetworkClient's Snapshot Interpolation settings in NetworkManager (#3411)
This commit is contained in:
parent
3535728786
commit
2f68666683
@ -1703,7 +1703,7 @@ public static void OnGUI()
|
||||
GUILayout.Box($"timeline: {localTimeline:F2}");
|
||||
GUILayout.Box($"buffer: {snapshots.Count}");
|
||||
GUILayout.Box($"timescale: {localTimescale:F2}");
|
||||
GUILayout.Box($"BTM: {bufferTimeMultiplier:F2}");
|
||||
GUILayout.Box($"BTM: {snapshotSettings.bufferTimeMultiplier:F2}");
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.EndArea();
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
@ -5,16 +6,28 @@ namespace Mirror
|
||||
{
|
||||
public static partial class NetworkClient
|
||||
{
|
||||
// snapshot interpolation settings /////////////////////////////////////
|
||||
// TODO expose the settings to the user later.
|
||||
// via NetMan or NetworkClientConfig or NetworkClient as component etc.
|
||||
public static SnapshotInterpolationSettings snapshotSettings = new SnapshotInterpolationSettings();
|
||||
|
||||
// decrease bufferTime at runtime to see the catchup effect.
|
||||
// increase to see slowdown.
|
||||
// 'double' so we can have very precise dynamic adjustment without rounding
|
||||
[Header("Snapshot Interpolation: Buffering")]
|
||||
[Tooltip("Local simulation is behind by sendInterval * multiplier seconds.\n\nThis guarantees that we always have enough snapshots in the buffer to mitigate lags & jitter.\n\nIncrease this if the simulation isn't smooth. By default, it should be around 2.")]
|
||||
public static double bufferTimeMultiplier = 2;
|
||||
public static double bufferTime => NetworkServer.sendInterval * bufferTimeMultiplier;
|
||||
// obsolete snapshot settings access
|
||||
// DEPRECATED 2023-03-11
|
||||
[Obsolete("NetworkClient snapshot interpolation settings were moved to NetworkClient.snapshotSettings.*")]
|
||||
public static double bufferTimeMultiplier => snapshotSettings.bufferTimeMultiplier;
|
||||
[Obsolete("NetworkClient snapshot interpolation settings were moved to NetworkClient.snapshotSettings.*")]
|
||||
public static float catchupNegativeThreshold => snapshotSettings.catchupNegativeThreshold;
|
||||
[Obsolete("NetworkClient snapshot interpolation settings were moved to NetworkClient.snapshotSettings.*")]
|
||||
public static float catchupPositiveThreshold => snapshotSettings.catchupPositiveThreshold;
|
||||
[Obsolete("NetworkClient snapshot interpolation settings were moved to NetworkClient.snapshotSettings.*")]
|
||||
public static double catchupSpeed => snapshotSettings.catchupSpeed;
|
||||
[Obsolete("NetworkClient snapshot interpolation settings were moved to NetworkClient.snapshotSettings.*")]
|
||||
public static double slowdownSpeed => snapshotSettings.slowdownSpeed;
|
||||
[Obsolete("NetworkClient snapshot interpolation settings were moved to NetworkClient.snapshotSettings.*")]
|
||||
public static int driftEmaDuration => snapshotSettings.driftEmaDuration;
|
||||
|
||||
// snapshot interpolation runtime data /////////////////////////////////
|
||||
public static double bufferTime => NetworkServer.sendInterval * snapshotSettings.bufferTimeMultiplier;
|
||||
|
||||
// <servertime, snaps>
|
||||
public static SortedList<double, TimeSnapshot> snapshots = new SortedList<double, TimeSnapshot>();
|
||||
@ -33,25 +46,7 @@ public static partial class NetworkClient
|
||||
internal static double localTimescale = 1;
|
||||
|
||||
// catchup /////////////////////////////////////////////////////////////
|
||||
// catchup thresholds in 'frames'.
|
||||
// half a frame might be too aggressive.
|
||||
[Header("Snapshot Interpolation: Catchup / Slowdown")]
|
||||
[Tooltip("Slowdown begins when the local timeline is moving too fast towards remote time. Threshold is in frames worth of snapshots.\n\nThis needs to be negative.\n\nDon't modify unless you know what you are doing.")]
|
||||
public static float catchupNegativeThreshold = -1; // careful, don't want to run out of snapshots
|
||||
|
||||
[Tooltip("Catchup begins when the local timeline is moving too slow and getting too far away from remote time. Threshold is in frames worth of snapshots.\n\nThis needs to be positive.\n\nDon't modify unless you know what you are doing.")]
|
||||
public static float catchupPositiveThreshold = 1;
|
||||
|
||||
[Tooltip("Local timeline acceleration in % while catching up.")]
|
||||
[Range(0, 1)]
|
||||
public static double catchupSpeed = 0.01f; // 1%
|
||||
|
||||
[Tooltip("Local timeline slowdown in % while slowing down.")]
|
||||
[Range(0, 1)]
|
||||
public static double slowdownSpeed = 0.01f; // 1%
|
||||
|
||||
[Tooltip("Catchup/Slowdown is adjusted over n-second exponential moving average.")]
|
||||
public static int driftEmaDuration = 1; // shouldn't need to modify this, but expose it anyway
|
||||
|
||||
// we use EMA to average the last second worth of snapshot time diffs.
|
||||
// manually averaging the last second worth of values with a for loop
|
||||
@ -103,8 +98,8 @@ static void InitTimeInterpolation()
|
||||
// initialize EMA with 'emaDuration' seconds worth of history.
|
||||
// 1 second holds 'sendRate' worth of values.
|
||||
// multiplied by emaDuration gives n-seconds.
|
||||
driftEma = new ExponentialMovingAverage(NetworkServer.sendRate * driftEmaDuration);
|
||||
deliveryTimeEma = new ExponentialMovingAverage(NetworkServer.sendRate * deliveryTimeEmaDuration);
|
||||
driftEma = new ExponentialMovingAverage(NetworkServer.sendRate * snapshotSettings.driftEmaDuration);
|
||||
deliveryTimeEma = new ExponentialMovingAverage(NetworkServer.sendRate * snapshotSettings.deliveryTimeEmaDuration);
|
||||
}
|
||||
|
||||
// server sends TimeSnapshotMessage every sendInterval.
|
||||
@ -131,14 +126,14 @@ public static void OnTimeSnapshot(TimeSnapshot snap)
|
||||
// Debug.Log($"NetworkClient: OnTimeSnapshot @ {snap.remoteTime:F3}");
|
||||
|
||||
// (optional) dynamic adjustment
|
||||
if (dynamicAdjustment)
|
||||
if (snapshotSettings.dynamicAdjustment)
|
||||
{
|
||||
// set bufferTime on the fly.
|
||||
// shows in inspector for easier debugging :)
|
||||
bufferTimeMultiplier = SnapshotInterpolation.DynamicAdjustment(
|
||||
snapshotSettings.bufferTimeMultiplier = SnapshotInterpolation.DynamicAdjustment(
|
||||
NetworkServer.sendInterval,
|
||||
deliveryTimeEma.StandardDeviation,
|
||||
dynamicAdjustmentTolerance
|
||||
snapshotSettings.dynamicAdjustmentTolerance
|
||||
);
|
||||
}
|
||||
|
||||
@ -150,11 +145,11 @@ public static void OnTimeSnapshot(TimeSnapshot snap)
|
||||
ref localTimescale,
|
||||
NetworkServer.sendInterval,
|
||||
bufferTime,
|
||||
catchupSpeed,
|
||||
slowdownSpeed,
|
||||
snapshotSettings.catchupSpeed,
|
||||
snapshotSettings.slowdownSpeed,
|
||||
ref driftEma,
|
||||
catchupNegativeThreshold,
|
||||
catchupPositiveThreshold,
|
||||
snapshotSettings.catchupNegativeThreshold,
|
||||
snapshotSettings.catchupPositiveThreshold,
|
||||
ref deliveryTimeEma);
|
||||
|
||||
// Debug.Log($"inserted TimeSnapshot remote={snap.remoteTime:F2} local={snap.localTime:F2} total={snapshots.Count}");
|
||||
|
@ -52,11 +52,11 @@ public NetworkConnectionToClient(int networkConnectionId)
|
||||
// initialize EMA with 'emaDuration' seconds worth of history.
|
||||
// 1 second holds 'sendRate' worth of values.
|
||||
// multiplied by emaDuration gives n-seconds.
|
||||
driftEma = new ExponentialMovingAverage(NetworkServer.sendRate * NetworkClient.driftEmaDuration);
|
||||
deliveryTimeEma = new ExponentialMovingAverage(NetworkServer.sendRate * NetworkClient.deliveryTimeEmaDuration);
|
||||
driftEma = new ExponentialMovingAverage(NetworkServer.sendRate * NetworkClient.snapshotSettings.driftEmaDuration);
|
||||
deliveryTimeEma = new ExponentialMovingAverage(NetworkServer.sendRate * NetworkClient.snapshotSettings.deliveryTimeEmaDuration);
|
||||
|
||||
// buffer limit should be at least multiplier to have enough in there
|
||||
snapshotBufferSizeLimit = Mathf.Max((int)NetworkClient.bufferTimeMultiplier, snapshotBufferSizeLimit);
|
||||
snapshotBufferSizeLimit = Mathf.Max((int)NetworkClient.snapshotSettings.bufferTimeMultiplier, snapshotBufferSizeLimit);
|
||||
}
|
||||
|
||||
public void OnTimeSnapshot(TimeSnapshot snapshot)
|
||||
@ -65,14 +65,14 @@ public void OnTimeSnapshot(TimeSnapshot snapshot)
|
||||
if (snapshots.Count >= snapshotBufferSizeLimit) return;
|
||||
|
||||
// (optional) dynamic adjustment
|
||||
if (NetworkClient.dynamicAdjustment)
|
||||
if (NetworkClient.snapshotSettings.dynamicAdjustment)
|
||||
{
|
||||
// set bufferTime on the fly.
|
||||
// shows in inspector for easier debugging :)
|
||||
bufferTimeMultiplier = SnapshotInterpolation.DynamicAdjustment(
|
||||
NetworkServer.sendInterval,
|
||||
deliveryTimeEma.StandardDeviation,
|
||||
NetworkClient.dynamicAdjustmentTolerance
|
||||
NetworkClient.snapshotSettings.dynamicAdjustmentTolerance
|
||||
);
|
||||
// Debug.Log($"[Server]: {name} delivery std={serverDeliveryTimeEma.StandardDeviation} bufferTimeMult := {bufferTimeMultiplier} ");
|
||||
}
|
||||
@ -85,11 +85,11 @@ public void OnTimeSnapshot(TimeSnapshot snapshot)
|
||||
ref remoteTimescale,
|
||||
NetworkServer.sendInterval,
|
||||
bufferTime,
|
||||
NetworkClient.catchupSpeed,
|
||||
NetworkClient.slowdownSpeed,
|
||||
NetworkClient.snapshotSettings.catchupSpeed,
|
||||
NetworkClient.snapshotSettings.slowdownSpeed,
|
||||
ref driftEma,
|
||||
NetworkClient.catchupNegativeThreshold,
|
||||
NetworkClient.catchupPositiveThreshold,
|
||||
NetworkClient.snapshotSettings.catchupNegativeThreshold,
|
||||
NetworkClient.snapshotSettings.catchupPositiveThreshold,
|
||||
ref deliveryTimeEma
|
||||
);
|
||||
}
|
||||
|
@ -118,6 +118,9 @@ public class NetworkManager : MonoBehaviour
|
||||
public static List<Transform> startPositions = new List<Transform>();
|
||||
public static int startPositionIndex;
|
||||
|
||||
[Header("Snapshot Interpolation")]
|
||||
public SnapshotInterpolationSettings snapshotSettings = new SnapshotInterpolationSettings();
|
||||
|
||||
[Header("Debug")]
|
||||
public bool timeInterpolationGui = false;
|
||||
|
||||
@ -257,6 +260,7 @@ bool IsServerOnlineSceneChangeNeeded() =>
|
||||
void ApplyConfiguration()
|
||||
{
|
||||
NetworkServer.tickRate = sendRate;
|
||||
NetworkClient.snapshotSettings = snapshotSettings;
|
||||
}
|
||||
|
||||
// full server setup code, without spawning objects yet
|
||||
|
@ -0,0 +1,68 @@
|
||||
// snapshot interpolation settings struct.
|
||||
// can easily be exposed in Unity inspectors.
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror
|
||||
{
|
||||
// class so we can define defaults easily
|
||||
[Serializable]
|
||||
public class SnapshotInterpolationSettings
|
||||
{
|
||||
// decrease bufferTime at runtime to see the catchup effect.
|
||||
// increase to see slowdown.
|
||||
// 'double' so we can have very precise dynamic adjustment without rounding
|
||||
[Header("Buffering")]
|
||||
[Tooltip("Local simulation is behind by sendInterval * multiplier seconds.\n\nThis guarantees that we always have enough snapshots in the buffer to mitigate lags & jitter.\n\nIncrease this if the simulation isn't smooth. By default, it should be around 2.")]
|
||||
public double bufferTimeMultiplier = 2;
|
||||
|
||||
// catchup /////////////////////////////////////////////////////////////
|
||||
// catchup thresholds in 'frames'.
|
||||
// half a frame might be too aggressive.
|
||||
[Header("Catchup / Slowdown")]
|
||||
[Tooltip("Slowdown begins when the local timeline is moving too fast towards remote time. Threshold is in frames worth of snapshots.\n\nThis needs to be negative.\n\nDon't modify unless you know what you are doing.")]
|
||||
public float catchupNegativeThreshold = -1; // careful, don't want to run out of snapshots
|
||||
|
||||
[Tooltip("Catchup begins when the local timeline is moving too slow and getting too far away from remote time. Threshold is in frames worth of snapshots.\n\nThis needs to be positive.\n\nDon't modify unless you know what you are doing.")]
|
||||
public float catchupPositiveThreshold = 1;
|
||||
|
||||
[Tooltip("Local timeline acceleration in % while catching up.")]
|
||||
[Range(0, 1)]
|
||||
public double catchupSpeed = 0.01f; // 1%
|
||||
|
||||
[Tooltip("Local timeline slowdown in % while slowing down.")]
|
||||
[Range(0, 1)]
|
||||
public double slowdownSpeed = 0.01f; // 1%
|
||||
|
||||
[Tooltip("Catchup/Slowdown is adjusted over n-second exponential moving average.")]
|
||||
public int driftEmaDuration = 1; // shouldn't need to modify this, but expose it anyway
|
||||
|
||||
// dynamic buffer time adjustment //////////////////////////////////////
|
||||
// dynamically adjusts bufferTimeMultiplier for smooth results.
|
||||
// to understand how this works, try this manually:
|
||||
//
|
||||
// - disable dynamic adjustment
|
||||
// - set jitter = 0.2 (20% is a lot!)
|
||||
// - notice some stuttering
|
||||
// - disable interpolation to see just how much jitter this really is(!)
|
||||
// - enable interpolation again
|
||||
// - manually increase bufferTimeMultiplier to 3-4
|
||||
// ... the cube slows down (blue) until it's smooth
|
||||
// - with dynamic adjustment enabled, it will set 4 automatically
|
||||
// ... the cube slows down (blue) until it's smooth as well
|
||||
//
|
||||
// note that 20% jitter is extreme.
|
||||
// for this to be perfectly smooth, set the safety tolerance to '2'.
|
||||
// but realistically this is not necessary, and '1' is enough.
|
||||
[Header("Dynamic Adjustment")]
|
||||
[Tooltip("Automatically adjust bufferTimeMultiplier for smooth results.\nSets a low multiplier on stable connections, and a high multiplier on jittery connections.")]
|
||||
public bool dynamicAdjustment = true;
|
||||
|
||||
[Tooltip("Safety buffer that is always added to the dynamic bufferTimeMultiplier adjustment.")]
|
||||
public float dynamicAdjustmentTolerance = 1; // 1 is realistically just fine, 2 is very very safe even for 20% jitter. can be half a frame too. (see above comments)
|
||||
|
||||
[Tooltip("Dynamic adjustment is computed over n-second exponential moving average standard deviation.")]
|
||||
public int deliveryTimeEmaDuration = 2; // 1-2s recommended to capture average delivery time
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f955b76b7956417088c03992b3622dc9
|
||||
timeCreated: 1678507210
|
Loading…
Reference in New Issue
Block a user