mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-15 14:52:35 +02:00
This fixes the issue where CurTime on the client would sometimes jump by massive values. It was caused by an underflow when the current tick would be before the most recently cached tick, due to time going in reverse.
265 lines
8.8 KiB
C#
265 lines
8.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using Robust.Shared.Interfaces.Timing;
|
|
using Robust.Shared.Utility;
|
|
using Robust.Shared.Log;
|
|
|
|
namespace Robust.Shared.Timing
|
|
{
|
|
/// <summary>
|
|
/// This holds main loop timing information and helper functions.
|
|
/// </summary>
|
|
public class GameTiming : IGameTiming
|
|
{
|
|
// number of sample frames to store for profiling
|
|
private const int NumFrames = 60;
|
|
|
|
private readonly IStopwatch _realTimer = new Stopwatch();
|
|
private readonly List<long> _realFrameTimes = new List<long>(NumFrames);
|
|
private TimeSpan _lastRealTime;
|
|
|
|
/// <summary>
|
|
/// Default constructor.
|
|
/// </summary>
|
|
public GameTiming()
|
|
{
|
|
// does nothing if timer is already running
|
|
_realTimer.Start();
|
|
|
|
Paused = false;
|
|
TickRate = NumFrames;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is program execution inside of the simulation update, or rendering?
|
|
/// </summary>
|
|
public bool InSimulation { get; set; }
|
|
|
|
/// <summary>
|
|
/// Is the simulation currently paused?
|
|
/// </summary>
|
|
public bool Paused { get; set; }
|
|
|
|
// Cached values for making CurTime not jump whenever the tickrate
|
|
// changes. This holds the last calculated value of CurTime, and the
|
|
// tick that the value was calculated for. The next time CurTime is
|
|
// calculated, it should only try to monotonically increase both of
|
|
// these values
|
|
//
|
|
// Notice that it starts from GameTick 1 - the "first tick" has no impact
|
|
// on timing
|
|
private (TimeSpan, GameTick) _cachedCurTimeInfo = (TimeSpan.Zero, GameTick.First);
|
|
|
|
/// <summary>
|
|
/// The current synchronized uptime of the simulation. Use this for in-game timing. This can be rewound for
|
|
/// prediction, and is affected by Paused and TimeScale.
|
|
/// </summary>
|
|
public TimeSpan CurTime
|
|
{
|
|
get
|
|
{
|
|
CacheCurTime();
|
|
|
|
var (time, lastTimeTick) = _cachedCurTimeInfo;
|
|
|
|
Debug.Assert(lastTimeTick == CurTick);
|
|
|
|
if (!InSimulation) // rendering can draw frames between ticks
|
|
return time + TickRemainder;
|
|
return time;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The current real uptime of the simulation. Use this for UI and out of game timing.
|
|
/// </summary>
|
|
public TimeSpan RealTime => _realTimer.Elapsed;
|
|
|
|
/// <summary>
|
|
/// The simulated time it took to render the last frame.
|
|
/// </summary>
|
|
public TimeSpan FrameTime => CalcFrameTime();
|
|
|
|
/// <summary>
|
|
/// The real time it took to render the last frame.
|
|
/// </summary>
|
|
public TimeSpan RealFrameTime { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Average real frame time over the last 50 frames.
|
|
/// </summary>
|
|
public TimeSpan RealFrameTimeAvg => TimeSpan.FromTicks((long)_realFrameTimes.Average());
|
|
|
|
/// <summary>
|
|
/// Standard Deviation of the real frame time over the last 50 frames.
|
|
/// </summary>
|
|
public TimeSpan RealFrameTimeStdDev => CalcRftStdDev();
|
|
|
|
/// <summary>
|
|
/// Average real FPS over the last 50 frames.
|
|
/// </summary>
|
|
public double FramesPerSecondAvg => CalcFpsAvg();
|
|
|
|
/// <summary>
|
|
/// The current simulation tick being processed.
|
|
/// </summary>
|
|
public GameTick CurTick { get; set; } = new GameTick(1); // Time always starts on the first tick
|
|
|
|
private byte _tickRate;
|
|
|
|
/// <summary>
|
|
/// The target ticks/second of the simulation.
|
|
/// </summary>
|
|
public byte TickRate
|
|
{
|
|
get => _tickRate;
|
|
set
|
|
{
|
|
// Check this, because TickRate is a divisor in the cache calculation
|
|
// The first time TickRate is set, no time will have passed anyways
|
|
if (_tickRate != 0)
|
|
// Cache BEFORE updating the tick rate, because ticks up until
|
|
// now have been on a different rate, so they count for a
|
|
// different amount of time
|
|
CacheCurTime();
|
|
|
|
_tickRate = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The length of a tick at the current TickRate. 1/TickRate.
|
|
/// </summary>
|
|
public TimeSpan TickPeriod => TimeSpan.FromTicks((long)(1.0 / TickRate * TimeSpan.TicksPerSecond));
|
|
|
|
/// <summary>
|
|
/// The remaining time left over after the last tick was ran.
|
|
/// </summary>
|
|
public TimeSpan TickRemainder { get; set; }
|
|
|
|
/// <summary>
|
|
/// Current graphics frame since init OpenGL which is taken as frame 1, from swapbuffer to swapbuffer. Useful to set a
|
|
/// conditional breakpoint on specific frames, and synchronize with OGL debugging tools that capture frames.
|
|
/// Depending on the tools used, this frame number will vary between 1 frame more or less due to how that tool is counting frames,
|
|
/// i.e. starting from 0 or 1, having a separate counter, etc. Available in timing debug panel.
|
|
/// </summary>
|
|
public uint CurFrame { get; set; } = 1;
|
|
|
|
/// <inheritdoc />
|
|
public float TickTimingAdjustment { get; set; } = 0;
|
|
|
|
/// <summary>
|
|
/// Ends the 'lap' of the timer, updating frame time info.
|
|
/// </summary>
|
|
public void StartFrame()
|
|
{
|
|
// calculate real timing info
|
|
var curRealTime = RealTime;
|
|
RealFrameTime = curRealTime - _lastRealTime;
|
|
_lastRealTime = curRealTime;
|
|
|
|
// update profiling
|
|
if (_realFrameTimes.Count >= NumFrames)
|
|
_realFrameTimes.RemoveAt(0);
|
|
_realFrameTimes.Add(RealFrameTime.Ticks);
|
|
}
|
|
|
|
private TimeSpan CalcFrameTime()
|
|
{
|
|
// calculate simulation FrameTime
|
|
if (InSimulation)
|
|
{
|
|
return TimeSpan.FromTicks(TickPeriod.Ticks);
|
|
}
|
|
else
|
|
{
|
|
return Paused ? TimeSpan.Zero : RealFrameTime;
|
|
}
|
|
}
|
|
|
|
// Calculate and store the current time value, based on the current tick rate.
|
|
// Call this whenever you want an updated value for CurTime, or when
|
|
// you change the TickRate
|
|
private void CacheCurTime()
|
|
{
|
|
var (cachedTime, lastTimeTick) = _cachedCurTimeInfo;
|
|
|
|
TimeSpan newTime;
|
|
|
|
if (CurTick.Value >= lastTimeTick.Value)
|
|
newTime = cachedTime + (TickPeriod * (CurTick.Value - lastTimeTick.Value));
|
|
else
|
|
newTime = cachedTime - (TickPeriod * (lastTimeTick.Value - CurTick.Value));
|
|
|
|
_cachedCurTimeInfo = (newTime, CurTick);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the real uptime of the server.
|
|
/// </summary>
|
|
public void ResetRealTime()
|
|
{
|
|
_realTimer.Restart();
|
|
_lastRealTime = TimeSpan.Zero;
|
|
}
|
|
|
|
public bool IsFirstTimePredicted { get; private set; } = true;
|
|
|
|
public void StartPastPrediction()
|
|
{
|
|
// Don't allow recursive predictions.
|
|
// Not sure if it's necessary yet and if not, great!
|
|
DebugTools.Assert(IsFirstTimePredicted);
|
|
|
|
IsFirstTimePredicted = false;
|
|
}
|
|
|
|
public void EndPastPrediction()
|
|
{
|
|
DebugTools.Assert(!IsFirstTimePredicted);
|
|
|
|
IsFirstTimePredicted = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the average FPS of the last 50 real frame times.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private double CalcFpsAvg()
|
|
{
|
|
if (_realFrameTimes.Count == 0)
|
|
return 0;
|
|
|
|
return 1 / (_realFrameTimes.Average() / TimeSpan.TicksPerSecond);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the standard deviation of the last 50 real frame times.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private TimeSpan CalcRftStdDev()
|
|
{
|
|
var sum = _realFrameTimes.Sum();
|
|
var count = _realFrameTimes.Count;
|
|
var avg = sum / (double)count;
|
|
double devSquared = 0.0f;
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
if (_realFrameTimes[i] == 0)
|
|
continue;
|
|
|
|
var ft = _realFrameTimes[i];
|
|
|
|
var dt = ft - avg;
|
|
|
|
devSquared += dt * dt;
|
|
}
|
|
|
|
var variance = devSquared / (count - 1);
|
|
return TimeSpan.FromTicks((long)Math.Sqrt(variance));
|
|
}
|
|
}
|
|
}
|