Skip to content

Commit

Permalink
fix: improved comments and log messages
Browse files Browse the repository at this point in the history
  • Loading branch information
SoftwareGuy authored and James-Frowen committed Feb 4, 2022
1 parent 91be1fa commit b73c3fe
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 145 deletions.
81 changes: 45 additions & 36 deletions source/InterpolationTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,25 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
namespace JamesFrowen.PositionSync
{
/// <summary>
/// Syncs time between server and client be receive regular message from server
/// Synchronizes time between server and client via regular messages between server and client.
/// <para>Can be used for snapshot interpolation</para>
/// </summary>
/// <remarks>
/// This class will speed up or slow down Client time scale based on if it is ahead of behind the lastest server time
/// This class will speed up or slow down the client time scale, depending if it is ahead or behind the lastest server time.
/// <para>
/// Every Update we add DeltaTime * TimeScale to client time
/// Every update we add DeltaTime * TimeScale to client time.
/// </para>
/// <para>
/// Every Update server sends message with its time<br/>
/// When client receives message it calculates difference between server time and local time<br/>
/// This difference is stored in a moving average so it is smoothed out
/// On the server, when an update is performed, the server will send a message back with its time.<br/>
/// When the client receives this message, it calculates the difference between server time and its own local time.<br/>
/// This difference is stored in a moving average, which is smoothed out.
/// </para>
/// <para>
/// If this difference is greater or less than a threshold then we speed up or slow down Client time scale<br/>
/// If difference is between threshold time is set back to normal scale
/// If the calculated difference is greater or less than a threshold then we adjust the client time scale by speeding up or slowing down.<br/>
/// If the calculated difference is between our defined threshold times, client time scale is set back to normal.
/// </para>
/// <para>
/// This Client time can then be used to snapshot interpolation using <c>InterpolationTime = ClientTime - Offset</c>
/// This client time can then be used to snapshot interpolation using <c>InterpolationTime = ClientTime - Offset</c>
/// </para>
/// <para>
/// Some other implementations include the offset in the time scale calculations itself,
Expand All @@ -62,26 +62,24 @@ public class InterpolationTime
{
static readonly ILogger logger = LogFactory.GetLogger<InterpolationTime>();



bool intialized;
/// <summary>
/// time client uses to interpolate
/// The time value that the client uses to interpolate
/// </summary>
float _clientTime;
/// <summary>
/// Multiples deltaTime by this scale each frame
/// The client will multiply deltaTime by this scale time value each frame
/// </summary>
float clientScaleTime;

readonly ExponentialMovingAverage diffAvg;

/// <summary>
/// how much above goalOffset diff is allowed to go before changing timescale
/// How much above the goalOffset difference are we allowed to go before changing the timescale
/// </summary>
readonly float positiveThreshold;
/// <summary>
/// how much below goalOffset diff is allowed to go before changing timescale
/// How much below the goalOffset difference are we allowed to go before changing the timescale
/// </summary>
readonly float negativeThreshold;

Expand All @@ -90,13 +88,14 @@ public class InterpolationTime
readonly float slowScale = 0.99f;

/// <summary>
/// if new time and previous time are this far apart then reset client time
/// Is the difference between previous time and new time too far apart?
/// If so, reset the client time.
/// </summary>
readonly float _skipAheadThreshold;

float _clientDelay;

// debug
// Used for debug purposes. Move along...
float _latestServerTime;

/// <summary>
Expand All @@ -108,7 +107,7 @@ public float ClientTime
get => _clientTime;
}
/// <summary>
/// Last time Received by server
/// Returns the last time received by the server
/// </summary>
public float LatestServerTime
{
Expand All @@ -129,10 +128,11 @@ public float ClientDelay
set => _clientDelay = value;
}

// Used for debug purposes. Move along...
public float DebugScale => clientScaleTime;

/// <param name="diffThreshold">how far off client time can be before changing its speed, Good value is half SyncInterval</param>
/// <param name="movingAverageCount">how many ticks used in average, increase or decrease with framerate</param>
/// <param name="diffThreshold">How far off client time can be before changing its speed. A good recommended value is half of SyncInterval.</param>
/// <param name="movingAverageCount">How many ticks are used for averaging purposes, you may need to increase or decrease with frame rate.</param>
public InterpolationTime(float tickInterval, float diffThreshold = 0.5f, float timeScale = 0.01f, float skipThreshold = 2.5f, float tickDelay = 2, int movingAverageCount = 30)
{
positiveThreshold = tickInterval * diffThreshold;
Expand All @@ -146,12 +146,12 @@ public InterpolationTime(float tickInterval, float diffThreshold = 0.5f, float t

diffAvg = new ExponentialMovingAverage(movingAverageCount);

// start at normal time scale
// Client should always start at normal time scale.
clientScaleTime = normalScale;
}

/// <summary>
/// Updates client time
/// Updates the client time.
/// </summary>
/// <param name="deltaTime"></param>
public void OnUpdate(float deltaTime)
Expand All @@ -165,35 +165,42 @@ public void OnUpdate(float deltaTime)
/// <param name="serverTime"></param>
public void OnMessage(float serverTime)
{
logger.Assert(serverTime > _latestServerTime, $"Received message out of order server:{serverTime}, new:{_latestServerTime}");
logger.Assert(serverTime > _latestServerTime, $"Received message out of order. Server Time: {serverTime} vs New Time: {_latestServerTime}");
_latestServerTime = serverTime;

// if first message set client time to server-diff
// reset stuff if too far behind
// If this is the first message, set the client time to the server difference.
// If we're too far behind, then we should reset things too.

// todo check this is correct
if (!intialized)
{
InitNew(serverTime);
return;
}

// Calculate the difference.
float diff = serverTime - _clientTime;

// Are we falling behind?
if (serverTime - _clientTime > _skipAheadThreshold)
{
logger.LogWarning($"Client fell behind, skipping ahead. server:{serverTime:0.00} diff:{diff:0.00}");
logger.LogWarning($"Client fell behind, skipping ahead. Server Time: {serverTime:0.00}, Difference: {diff:0.00}");
InitNew(serverTime);
return;
}

diffAvg.Add(diff);

// Adjust the client time scale with the appropriate value.
AdjustClientTimeScale((float)diffAvg.Value);

//todo add trace level
if (logger.LogEnabled()) logger.Log($"st {serverTime:0.00} ct {_clientTime:0.00} diff {diff * 1000:0.0}, wanted:{diffAvg.Value * 1000:0.0}, scale:{clientScaleTime}");
// todo add trace level
if (logger.LogEnabled()) logger.Log($"st: {serverTime:0.00}, ct: {_clientTime:0.00}, diff: {diff * 1000:0.0}, wanted: {diffAvg.Value * 1000:0.0}, scale: {clientScaleTime}");
}

/// <summary>
/// Initializes and resets the system.
/// </summary>
private void InitNew(float serverTime)
{
_clientTime = serverTime;
Expand All @@ -202,21 +209,23 @@ private void InitNew(float serverTime)
intialized = true;
}

/// <summary>
/// Adjusts the client time scale based on the provided difference.
/// </summary>
private void AdjustClientTimeScale(float diff)
{
// diff is server-client,
// if positive then server is ahead, => we can run client faster to catch up
// if negative then server is behind, => we need to run client slow to not run out of spanshots

// we want diffVsGoal to be as close to 0 as possible
// Difference is calculated between server and client.
// So if that difference is positive, we can run the client faster to catch up.
// However, if it's negative, we need to slow the client down otherwise we run out of snapshots.
// Ideally, we want the difference vs the goal to be as close to 0 as possible.

// server ahead, speed up client
// Server's ahead of us, we need to speed up.
if (diff > positiveThreshold)
clientScaleTime = fastScale;
// server behind, slow down client
// Server is falling behind us, we need to slow down.
else if (diff < negativeThreshold)
clientScaleTime = slowScale;
// close enough
// Server and client are on par ("close enough"). Run at normal speed.
else
clientScaleTime = normalScale;
}
Expand Down
46 changes: 22 additions & 24 deletions source/SnapshotBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,38 +89,36 @@ Snapshot Last
public void AddSnapShot(T state, double serverTime)
{
if (!IsEmpty && serverTime < Last.time)
{
throw new ArgumentException($"Can not add Snapshot to buffer out of order, last t={Last.time:0.000}, new t={serverTime:0.000}");
}
throw new ArgumentException($"Can not add snapshot to buffer. This would cause the buffer to be out of order. Last t={Last.time:0.000}, new t={serverTime:0.000}");

buffer.Add(new Snapshot(state, serverTime));
}

/// <summary>
/// Gets snapshot to use for interpolation
/// <para>this method should not be called when there are no snapshots in buffer</para>
/// Gets a snapshot to use for interpolation purposes.
/// <para>This method should not be called when there are no snapshots in the buffer.</para>
/// </summary>
/// <param name="now"></param>
/// <returns></returns>
public T GetLinearInterpolation(double now)
{
if (buffer.Count == 0)
{
throw new InvalidOperationException("No snapshots in buffer");
}
throw new InvalidOperationException("No snapshots in buffer.");

// first snapshot
if (buffer.Count == 1)
{
if (logger.LogEnabled()) logger.Log("First snapshot");
if (logger.LogEnabled())
logger.Log("First snapshot");

return First.state;
}

// if first snapshot is after now, there is no "from", so return same as first snapshot
if (First.time > now)
{
if (logger.LogEnabled()) logger.Log($"No snapshots for t={now:0.000}, using earliest t={buffer[0].time:0.000}");
if (logger.LogEnabled())
logger.Log($"No snapshots for t = {now:0.000}, using earliest t = {buffer[0].time:0.000}");

return First.state;
}
Expand All @@ -130,11 +128,12 @@ public T GetLinearInterpolation(double now)
// there could be no new data from either lag or because object hasn't moved
if (Last.time < now)
{
if (logger.LogEnabled()) logger.Log($"No snapshots for t={now:0.000}, using first t={buffer[0].time:0.000} last t={Last.time:0.000}");
if (logger.LogEnabled())
logger.Log($"No snapshots for t = {now:0.000}, using first t = {buffer[0].time:0.000}, last t = {Last.time:0.000}");
return Last.state;
}

// edge cases are returned about, if code gets to this for loop then a valid from/to should exist
// edge cases are returned about, if code gets to this for loop then a valid from/to should exist...
for (int i = 0; i < buffer.Count - 1; i++)
{
Snapshot from = buffer[i];
Expand All @@ -153,45 +152,45 @@ public T GetLinearInterpolation(double now)
}
}

// If not, then this is our final stand.
logger.LogError("Should never be here! Code should have return from if or for loop above.");
return Last.state;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double Clamp01(double v)
{
if (v < 0) { return 0; }
if (v > 1) { return 1; }
else { return v; }
if (v < 0) return 0;
if (v > 1) return 1;
else return v;
}

/// <summary>
/// removes snapshots older than <paramref name="oldTime"/>, but keeps atleast <paramref name="keepCount"/> snapshots in buffer that are older than oldTime
/// <para>
/// Keep atleast 1 snapshot older than old time so there is something to interoplate from
/// </para>
/// Removes snapshots older than <paramref name="oldTime"/>.
/// </summary>
/// <param name="oldTime"></param>
/// <param name="keepCount">minium number of snapshots to keep in buffer</param>
public void RemoveOldSnapshots(float oldTime)
{
// loop from newest to oldest
// Loop from newest to oldest...
for (int i = buffer.Count - 1; i >= 0; i--)
{
// older than oldTime
// Is this one older than oldTime? If so, evict it.
if (buffer[i].time < oldTime)
{
buffer.RemoveAt(i);
}
}
}

/// <summary>
/// Clears the snapshots buffer.
/// </summary>
public void ClearBuffer()
{
buffer.Clear();
}


// Used for debug purposes. Move along...
public string ToDebugString(float now)
{
if (buffer.Count == 0) { return "Buffer Empty"; }
Expand All @@ -211,7 +210,6 @@ public string ToDebugString(float now)
}
}


builder.AppendLine($" {i}: {buffer[i].time:0.000}");
}
return builder.ToString();
Expand Down
Loading

0 comments on commit b73c3fe

Please sign in to comment.