decouple fixed timestep from draw, add frame cap
parent
5a9709c843
commit
d2a51ce524
|
@ -0,0 +1,14 @@
|
||||||
|
namespace MoonWorks
|
||||||
|
{
|
||||||
|
public enum FramerateMode
|
||||||
|
{
|
||||||
|
Uncapped,
|
||||||
|
Capped
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct FramerateSettings
|
||||||
|
{
|
||||||
|
public FramerateMode Mode;
|
||||||
|
public int Cap;
|
||||||
|
}
|
||||||
|
}
|
75
src/Game.cs
75
src/Game.cs
|
@ -18,7 +18,8 @@ namespace MoonWorks
|
||||||
private Stopwatch gameTimer;
|
private Stopwatch gameTimer;
|
||||||
private TimeSpan timestep;
|
private TimeSpan timestep;
|
||||||
private long previousTicks = 0;
|
private long previousTicks = 0;
|
||||||
TimeSpan accumulatedElapsedTime = TimeSpan.Zero;
|
TimeSpan accumulatedUpdateTime = TimeSpan.Zero;
|
||||||
|
TimeSpan accumulatedDrawTime = TimeSpan.Zero;
|
||||||
// must be a power of 2 so we can do a bitmask optimization when checking worst case
|
// must be a power of 2 so we can do a bitmask optimization when checking worst case
|
||||||
private const int PREVIOUS_SLEEP_TIME_COUNT = 128;
|
private const int PREVIOUS_SLEEP_TIME_COUNT = 128;
|
||||||
private const int SLEEP_TIME_MASK = PREVIOUS_SLEEP_TIME_COUNT - 1;
|
private const int SLEEP_TIME_MASK = PREVIOUS_SLEEP_TIME_COUNT - 1;
|
||||||
|
@ -26,6 +27,9 @@ namespace MoonWorks
|
||||||
private int sleepTimeIndex = 0;
|
private int sleepTimeIndex = 0;
|
||||||
private TimeSpan worstCaseSleepPrecision = TimeSpan.FromMilliseconds(1);
|
private TimeSpan worstCaseSleepPrecision = TimeSpan.FromMilliseconds(1);
|
||||||
|
|
||||||
|
private bool FramerateCapped = false;
|
||||||
|
private TimeSpan FramerateCapTimeSpan = TimeSpan.Zero;
|
||||||
|
|
||||||
public Window Window { get; }
|
public Window Window { get; }
|
||||||
public GraphicsDevice GraphicsDevice { get; }
|
public GraphicsDevice GraphicsDevice { get; }
|
||||||
public AudioDevice AudioDevice { get; }
|
public AudioDevice AudioDevice { get; }
|
||||||
|
@ -42,6 +46,7 @@ namespace MoonWorks
|
||||||
public Game(
|
public Game(
|
||||||
WindowCreateInfo windowCreateInfo,
|
WindowCreateInfo windowCreateInfo,
|
||||||
PresentMode presentMode,
|
PresentMode presentMode,
|
||||||
|
FramerateSettings framerateSettings,
|
||||||
int targetTimestep = 60,
|
int targetTimestep = 60,
|
||||||
bool debugMode = false
|
bool debugMode = false
|
||||||
)
|
)
|
||||||
|
@ -49,6 +54,13 @@ namespace MoonWorks
|
||||||
timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
|
timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
|
||||||
gameTimer = Stopwatch.StartNew();
|
gameTimer = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
FramerateCapped = framerateSettings.Mode == FramerateMode.Capped;
|
||||||
|
|
||||||
|
if (FramerateCapped)
|
||||||
|
{
|
||||||
|
FramerateCapTimeSpan = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / framerateSettings.Cap);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < previousSleepTimes.Length; i += 1)
|
for (int i = 0; i < previousSleepTimes.Length; i += 1)
|
||||||
{
|
{
|
||||||
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
|
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
|
||||||
|
@ -90,61 +102,63 @@ namespace MoonWorks
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void Update(TimeSpan delta);
|
protected abstract void Update(TimeSpan delta);
|
||||||
protected abstract void Draw(TimeSpan delta, double alpha);
|
protected abstract void Draw(double alpha);
|
||||||
|
|
||||||
private void Tick()
|
private void Tick()
|
||||||
{
|
{
|
||||||
AdvanceElapsedTime();
|
AdvanceElapsedTime();
|
||||||
|
|
||||||
/* We want to wait until the next frame,
|
if (FramerateCapped)
|
||||||
* but we don't want to oversleep. Requesting repeated 1ms sleeps and
|
|
||||||
* seeing how long we actually slept for lets us estimate the worst case
|
|
||||||
* sleep precision so we don't oversleep the next frame.
|
|
||||||
*/
|
|
||||||
while (accumulatedElapsedTime + worstCaseSleepPrecision < timestep)
|
|
||||||
{
|
{
|
||||||
System.Threading.Thread.Sleep(1);
|
/* We want to wait until the framerate cap,
|
||||||
TimeSpan timeAdvancedSinceSleeping = AdvanceElapsedTime();
|
* but we don't want to oversleep. Requesting repeated 1ms sleeps and
|
||||||
UpdateEstimatedSleepPrecision(timeAdvancedSinceSleeping);
|
* seeing how long we actually slept for lets us estimate the worst case
|
||||||
}
|
* sleep precision so we don't oversleep the next frame.
|
||||||
|
*/
|
||||||
|
while (accumulatedDrawTime + worstCaseSleepPrecision < FramerateCapTimeSpan)
|
||||||
|
{
|
||||||
|
System.Threading.Thread.Sleep(1);
|
||||||
|
TimeSpan timeAdvancedSinceSleeping = AdvanceElapsedTime();
|
||||||
|
UpdateEstimatedSleepPrecision(timeAdvancedSinceSleeping);
|
||||||
|
}
|
||||||
|
|
||||||
/* Now that we have slept into the sleep precision threshold, we need to wait
|
/* Now that we have slept into the sleep precision threshold, we need to wait
|
||||||
* for just a little bit longer until the target elapsed time has been reached.
|
* for just a little bit longer until the target elapsed time has been reached.
|
||||||
* SpinWait(1) works by pausing the thread for very short intervals, so it is
|
* SpinWait(1) works by pausing the thread for very short intervals, so it is
|
||||||
* an efficient and time-accurate way to wait out the rest of the time.
|
* an efficient and time-accurate way to wait out the rest of the time.
|
||||||
*/
|
*/
|
||||||
while (accumulatedElapsedTime < timestep)
|
while (accumulatedDrawTime < FramerateCapTimeSpan)
|
||||||
{
|
{
|
||||||
System.Threading.Thread.SpinWait(1);
|
System.Threading.Thread.SpinWait(1);
|
||||||
AdvanceElapsedTime();
|
AdvanceElapsedTime();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we are going to perform an update, let's handle SDL events.
|
// Now that we are going to perform an update, let's handle SDL events.
|
||||||
HandleSDLEvents();
|
HandleSDLEvents();
|
||||||
|
|
||||||
// Do not let any step take longer than our maximum.
|
// Do not let any step take longer than our maximum.
|
||||||
if (accumulatedElapsedTime > MAX_DELTA_TIME)
|
if (accumulatedUpdateTime > MAX_DELTA_TIME)
|
||||||
{
|
{
|
||||||
accumulatedElapsedTime = MAX_DELTA_TIME;
|
accumulatedUpdateTime = MAX_DELTA_TIME;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!quit)
|
if (!quit)
|
||||||
{
|
{
|
||||||
while (accumulatedElapsedTime >= timestep)
|
while (accumulatedUpdateTime >= timestep)
|
||||||
{
|
{
|
||||||
Inputs.Mouse.Wheel = 0;
|
|
||||||
|
|
||||||
Inputs.Update();
|
Inputs.Update();
|
||||||
AudioDevice.Update();
|
AudioDevice.Update();
|
||||||
|
|
||||||
Update(timestep);
|
Update(timestep);
|
||||||
|
|
||||||
accumulatedElapsedTime -= timestep;
|
accumulatedUpdateTime -= timestep;
|
||||||
}
|
}
|
||||||
|
|
||||||
var alpha = accumulatedElapsedTime / timestep;
|
var alpha = accumulatedUpdateTime / timestep;
|
||||||
|
|
||||||
Draw(timestep, alpha);
|
Draw(alpha);
|
||||||
|
accumulatedDrawTime -= FramerateCapTimeSpan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +215,8 @@ namespace MoonWorks
|
||||||
{
|
{
|
||||||
long currentTicks = gameTimer.Elapsed.Ticks;
|
long currentTicks = gameTimer.Elapsed.Ticks;
|
||||||
TimeSpan timeAdvanced = TimeSpan.FromTicks(currentTicks - previousTicks);
|
TimeSpan timeAdvanced = TimeSpan.FromTicks(currentTicks - previousTicks);
|
||||||
accumulatedElapsedTime += timeAdvanced;
|
accumulatedUpdateTime += timeAdvanced;
|
||||||
|
accumulatedDrawTime += timeAdvanced;
|
||||||
previousTicks = currentTicks;
|
previousTicks = currentTicks;
|
||||||
return timeAdvanced;
|
return timeAdvanced;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue