// UnityEngine.Debug.Log($"sendInterval={sendInterval:F3} jitter std={jitterStandardDeviation:F3} => that is ~{multiples:F1} x sendInterval + {dynamicAdjustmentTolerance} => dynamic bufferTimeMultiplier={safezone}");
returnsafezone;
}
// helper function to insert a snapshot if it doesn't exist yet.
// extra function so we can use it for both cases:
// NetworkClient global timeline insertions & adjustments via Insert<T>.
// NetworkBehaviour local insertion without any time adjustments.
publicstaticboolInsertIfNotExists<T>(
SortedList<double,T>buffer,// snapshot buffer
intbufferLimit,// don't grow infinitely
Tsnapshot)// the newly received snapshot
whereT:Snapshot
{
// slow clients may not be able to process incoming snapshots fast enough.
// infinitely growing snapshots would make it even worse.
// for example, run NetworkRigidbodyBenchmark while deep profiling client.
// the client just grows and reallocates the buffer forever.
if(buffer.Count>=bufferLimit)returnfalse;
// SortedList does not allow duplicates.
// we don't need to check ContainsKey (which is expensive).
// simply add and compare count before/after for the return value.
//if (buffer.ContainsKey(snapshot.remoteTime)) return false; // too expensive
// buffer.Add(snapshot.remoteTime, snapshot); // throws if key exists
intbefore=buffer.Count;
buffer[snapshot.remoteTime]=snapshot;// overwrites if key exists
returnbuffer.Count>before;
}
// clamp timeline for cases where it gets too far behind.
// for example, a client app may go into the background and get updated
// with 1hz for a while. by the time it's back it's at least 30 frames
// behind, possibly more if the transport also queues up. In this
// scenario, at 1% catch up it took around 20+ seconds to finally catch
// up. For these kinds of scenarios it will be better to snap / clamp.
//
// to reproduce, try snapshot interpolation demo and press the button to
// simulate the client timeline at multiple seconds behind. it'll take
// a long time to catch up if the timeline is a long time behind.
publicstaticdoubleTimelineClamp(
doublelocalTimeline,
doublebufferTime,
doublelatestRemoteTime)
{
// we want local timeline to always be 'bufferTime' behind remote.
doubletargetTime=latestRemoteTime-bufferTime;
// we define a boundary of 'bufferTime' around the target time.
// this is where catchup / slowdown will happen.
// outside of the area, we clamp.
doublelowerBound=targetTime-bufferTime;// how far behind we can get
doubleupperBound=targetTime+bufferTime;// how far ahead we can get
// 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.")]
publicdoublebufferTimeMultiplier=2;
[Tooltip("If a client can't process snapshots fast enough, don't store too many.")]
[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.")]
publicfloatcatchupNegativeThreshold=-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.")]
publicfloatcatchupPositiveThreshold=1;
[Tooltip("Local timeline acceleration in % while catching up.")]
[Range(0,1)]
publicdoublecatchupSpeed=0.02f;// see snap interp demo. 1% is too slow.
[Tooltip("Local timeline slowdown in % while slowing down.")]
[Range(0,1)]
publicdoubleslowdownSpeed=0.04f;// slow down a little faster so we don't encounter empty buffer (= jitter)
[Tooltip("Catchup/Slowdown is adjusted over n-second exponential moving average.")]
publicintdriftEmaDuration=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.")]
publicbooldynamicAdjustment=true;
[Tooltip("Safety buffer that is always added to the dynamic bufferTimeMultiplier adjustment.")]
publicfloatdynamicAdjustmentTolerance=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.")]
publicintdeliveryTimeEmaDuration=2;// 1-2s recommended to capture average delivery time
thrownewSystem.ArgumentOutOfRangeException(nameof(arrayIndex),"Array Index Out of Range");
if(array.Length-arrayIndex<Count)
thrownewSystem.ArgumentException("The number of items in the SyncDictionary is greater than the available space from arrayIndex to the end of the destination array");