Files
RobustToolbox/Robust.Shared/Timing/GameTiming.cs
Pieter-Jan Briers 0c8f869cb4 Prediction (#1027)
2020-04-18 17:35:54 +02:00

215 lines
7.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.Utility;
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; }
/// <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 => CalcCurTime();
/// <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
/// <summary>
/// The target ticks/second of the simulation.
/// </summary>
public byte TickRate { get; set; }
/// <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;
}
}
private TimeSpan CalcCurTime()
{
// calculate simulation CurTime
var time = TimeSpan.FromTicks(TickPeriod.Ticks * CurTick.Value);
if (!InSimulation) // rendering can draw frames between ticks
return time + TickRemainder;
return time;
}
/// <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));
}
}
}