-
Notifications
You must be signed in to change notification settings - Fork 429
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6327 from smoogipoo/headless-thread-rate-matching
Make time move along at the same rate in all threads during headless execution
- Loading branch information
Showing
2 changed files
with
59 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,15 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
#nullable disable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using osu.Framework.Configuration; | ||
using osu.Framework.Graphics.Rendering.Dummy; | ||
using osu.Framework.Input.Handlers; | ||
using osu.Framework.Logging; | ||
using osu.Framework.Threading; | ||
using osu.Framework.Timing; | ||
|
||
namespace osu.Framework.Platform | ||
|
@@ -21,7 +22,7 @@ public class HeadlessGameHost : DesktopGameHost | |
public const double CLOCK_RATE = 1000.0 / 30; | ||
|
||
private readonly bool realtime; | ||
private IFrameBasedClock customClock; | ||
private IFrameBasedClock? customClock; | ||
|
||
protected override IFrameBasedClock SceneGraphClock => customClock ?? base.SceneGraphClock; | ||
|
||
|
@@ -41,15 +42,15 @@ public override bool PresentFileExternally(string filename) | |
|
||
public override IEnumerable<string> UserStoragePaths => new[] { "./headless/" }; | ||
|
||
public HeadlessGameHost(string gameName = null, HostOptions options = null, bool realtime = true) | ||
public HeadlessGameHost(string? gameName = null, HostOptions? options = null, bool realtime = true) | ||
: base(gameName ?? Guid.NewGuid().ToString(), options) | ||
{ | ||
this.realtime = realtime; | ||
} | ||
|
||
protected override bool RequireWindowExists => false; | ||
|
||
protected override IWindow CreateWindow(GraphicsSurfaceType preferredSurface) => null; | ||
protected override IWindow CreateWindow(GraphicsSurfaceType preferredSurface) => null!; | ||
|
||
protected override Clipboard CreateClipboard() => new HeadlessClipboard(); | ||
|
||
|
@@ -78,7 +79,7 @@ protected override void SetupForRun() | |
|
||
if (!realtime) | ||
{ | ||
customClock = new FramedClock(new FastClock(CLOCK_RATE)); | ||
customClock = new FramedClock(new FastClock(CLOCK_RATE, Threads.ToArray())); | ||
|
||
// time is incremented per frame, rather than based on the real-world time. | ||
// therefore our goal is to run frames as fast as possible. | ||
|
@@ -108,19 +109,62 @@ protected override void UpdateFrame() | |
private class FastClock : IClock | ||
{ | ||
private readonly double increment; | ||
|
||
private readonly GameThread[] gameThreads; | ||
private readonly ulong[] gameThreadLastFrames; | ||
|
||
private readonly Stopwatch stopwatch = new Stopwatch(); | ||
|
||
private double time; | ||
|
||
/// <summary> | ||
/// A clock which increments each time <see cref="CurrentTime"/> is requested. | ||
/// Run fast. Run consistent. | ||
/// </summary> | ||
/// <param name="increment">Milliseconds we should increment the clock by each time the time is requested.</param> | ||
public FastClock(double increment) | ||
/// <param name="gameThreads">The game threads.</param> | ||
public FastClock(double increment, GameThread[] gameThreads) | ||
{ | ||
this.increment = increment; | ||
this.gameThreads = gameThreads; | ||
gameThreadLastFrames = new ulong[gameThreads.Length]; | ||
} | ||
|
||
public double CurrentTime | ||
{ | ||
get | ||
{ | ||
double realElapsedTime = stopwatch.Elapsed.TotalMilliseconds; | ||
stopwatch.Restart(); | ||
|
||
if (allThreadsHaveProgressed) | ||
{ | ||
for (int i = 0; i < gameThreads.Length; i++) | ||
gameThreadLastFrames[i] = gameThreads[i].FrameIndex; | ||
|
||
// Increment time at the expedited rate. | ||
return time += increment; | ||
} | ||
|
||
// Fall back to real time to ensure we don't break random tests that expect threads to be running. | ||
return time += realElapsedTime; | ||
} | ||
} | ||
|
||
private bool allThreadsHaveProgressed | ||
{ | ||
get | ||
{ | ||
for (int i = 0; i < gameThreads.Length; i++) | ||
{ | ||
if (gameThreads[i].FrameIndex == gameThreadLastFrames[i]) | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
} | ||
|
||
public double CurrentTime => time += increment; | ||
public double Rate => 1; | ||
public bool IsRunning => true; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters