+12
-9
README.md
+12
-9
README.md
···
11
11
12
12
A game framework written with [osu!](https://github.com/ppy/osu) in mind.
13
13
14
-
## Requirements
14
+
## Developing a game using osu!framework
15
+
16
+
If you are interested in **creating a project** using the framework, please start from the [getting started](https://github.com/ppy/osu-framework/wiki/Setting-up-your-first-project) wiki resources (or jump straight over to the [project templates](https://github.com/ppy/osu-framework/tree/master/osu.Framework.Templates). You can either start off from an empty project, or take a peek at a working sample game. Either way, full project structure, cross-platform support, and a testing setup are included!
15
17
16
-
- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) or higher installed.
17
-
- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
18
-
- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs.
19
-
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
18
+
The rest of the information on this page is related to working *on* the framework, not *using* it!
20
19
21
20
## Objectives
22
21
···
26
25
- Common elements used by games (texture caching, font loading) will be automatically initialised at runtime.
27
26
- Allow for isolated development of components via a solid testing environment (`VisualTests` and `TestCases`). Check the [wiki](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) for more information on how these can be used to streamline development.
28
27
28
+
## Requirements
29
+
30
+
- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) or higher installed.
31
+
- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
32
+
- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs.
33
+
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
34
+
29
35
### Building
30
36
31
37
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing).
···
54
60
55
61
The BASS audio library (a dependency of this framework) is a commercial product. While it is free for non-commercial use, please ensure to [obtain a valid licence](http://www.un4seen.com/bass.html#license) if you plan on distributing any application using it commercially.
56
62
57
-
## Developing a game using osu!framework
58
-
59
-
If you want to get started making your own game project using osu!framework, check out our [project templates](https://github.com/ppy/osu-framework/tree/master/osu.Framework.Templates). You can either start off from an empty project, or take a peek at a working sample game. Either way, full project structure, cross-platform support, and testing setup are included!
60
-
61
63
## Projects that use osu!framework
62
64
63
65
[osu!](https://github.com/ppy/osu) – rhythm is just a *click* away!
···
75
77
- Must be a GitHub link (i.e. your project is open source)
76
78
- Must be actively developed (and have executable releases)
77
79
-->
80
+
+4
-1
osu.Framework.Android/AndroidGameHost.cs
+4
-1
osu.Framework.Android/AndroidGameHost.cs
···
59
59
60
60
public override Storage GetStorage(string path) => new AndroidStorage(path, this);
61
61
62
-
public override string UserStoragePath => Application.Context.GetExternalFilesDir(string.Empty).ToString();
62
+
public override IEnumerable<string> UserStoragePaths => new[]
63
+
{
64
+
Application.Context.GetExternalFilesDir(string.Empty).ToString()
65
+
};
63
66
64
67
public override void OpenFileExternally(string filename)
65
68
=> throw new NotImplementedException();
+150
osu.Framework.Tests/Platform/UserStorageLookupTest.cs
+150
osu.Framework.Tests/Platform/UserStorageLookupTest.cs
···
1
+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2
+
// See the LICENCE file in the repository root for full licence text.
3
+
4
+
using System;
5
+
using System.Collections.Generic;
6
+
using System.IO;
7
+
using NUnit.Framework;
8
+
using osu.Framework.Testing;
9
+
10
+
namespace osu.Framework.Tests.Platform
11
+
{
12
+
[TestFixture]
13
+
public class UserStorageLookupTest
14
+
{
15
+
private static readonly string path1 = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, "path1");
16
+
private static readonly string path2 = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, "path2");
17
+
18
+
private const string game_name = "test_game";
19
+
20
+
[TearDown]
21
+
public void TearDown()
22
+
{
23
+
try
24
+
{
25
+
File.Delete(path1);
26
+
File.Delete(path2);
27
+
}
28
+
catch
29
+
{
30
+
}
31
+
32
+
try
33
+
{
34
+
Directory.Delete(path1, true);
35
+
Directory.Delete(path2, true);
36
+
}
37
+
catch
38
+
{
39
+
}
40
+
}
41
+
42
+
[Test]
43
+
public void TestFirstBaseExisting()
44
+
{
45
+
Directory.CreateDirectory(path1);
46
+
47
+
using (var host = new StorageLookupHeadlessGameHost())
48
+
{
49
+
runHost(host);
50
+
Assert.IsTrue(host.Storage.GetFullPath(string.Empty).StartsWith(path1, StringComparison.Ordinal));
51
+
}
52
+
}
53
+
54
+
[Test]
55
+
public void TestSecondBaseExistingStillPrefersFirst()
56
+
{
57
+
Directory.CreateDirectory(path2);
58
+
59
+
using (var host = new StorageLookupHeadlessGameHost())
60
+
{
61
+
runHost(host);
62
+
Assert.IsTrue(host.Storage.GetFullPath(string.Empty).StartsWith(path1, StringComparison.Ordinal));
63
+
}
64
+
}
65
+
66
+
[Test]
67
+
public void TestSecondBaseUsedIfFirstFails()
68
+
{
69
+
// write a file so directory creation fails.
70
+
File.WriteAllText(path1, "");
71
+
72
+
using (var host = new StorageLookupHeadlessGameHost())
73
+
{
74
+
runHost(host);
75
+
Assert.IsTrue(host.Storage.GetFullPath(string.Empty).StartsWith(path2, StringComparison.Ordinal));
76
+
}
77
+
}
78
+
79
+
[Test]
80
+
public void TestFirstDataExisting()
81
+
{
82
+
Directory.CreateDirectory(Path.Combine(path1, game_name));
83
+
84
+
using (var host = new StorageLookupHeadlessGameHost())
85
+
{
86
+
runHost(host);
87
+
Assert.IsTrue(host.Storage.GetFullPath(string.Empty).StartsWith(path1, StringComparison.Ordinal));
88
+
}
89
+
}
90
+
91
+
[Test]
92
+
public void TestSecondDataExisting()
93
+
{
94
+
Directory.CreateDirectory(Path.Combine(path2, game_name));
95
+
96
+
using (var host = new StorageLookupHeadlessGameHost())
97
+
{
98
+
runHost(host);
99
+
Assert.IsTrue(host.Storage.GetFullPath(string.Empty).StartsWith(path2, StringComparison.Ordinal));
100
+
}
101
+
}
102
+
103
+
[Test]
104
+
public void TestPrefersFirstData()
105
+
{
106
+
Directory.CreateDirectory(Path.Combine(path1, game_name));
107
+
Directory.CreateDirectory(Path.Combine(path2, game_name));
108
+
109
+
using (var host = new StorageLookupHeadlessGameHost())
110
+
{
111
+
runHost(host);
112
+
Assert.IsTrue(host.Storage.GetFullPath(string.Empty).StartsWith(path1, StringComparison.Ordinal));
113
+
}
114
+
}
115
+
116
+
[Test]
117
+
public void TestPrefersSecondDataOverFirstBase()
118
+
{
119
+
Directory.CreateDirectory(path1);
120
+
Directory.CreateDirectory(Path.Combine(path2, game_name));
121
+
122
+
using (var host = new StorageLookupHeadlessGameHost())
123
+
{
124
+
runHost(host);
125
+
Assert.IsTrue(host.Storage.GetFullPath(string.Empty).StartsWith(path2, StringComparison.Ordinal));
126
+
}
127
+
}
128
+
129
+
private static void runHost(StorageLookupHeadlessGameHost host)
130
+
{
131
+
TestGame game = new TestGame();
132
+
game.Schedule(() => game.Exit());
133
+
host.Run(game);
134
+
}
135
+
136
+
private class StorageLookupHeadlessGameHost : TestRunHeadlessGameHost
137
+
{
138
+
public StorageLookupHeadlessGameHost()
139
+
: base(game_name)
140
+
{
141
+
}
142
+
143
+
public override IEnumerable<string> UserStoragePaths => new[]
144
+
{
145
+
path1,
146
+
path2,
147
+
};
148
+
}
149
+
}
150
+
}
+19
osu.Framework.Tests/Visual/Sprites/TestSceneVideo.cs
+19
osu.Framework.Tests/Visual/Sprites/TestSceneVideo.cs
···
95
95
AddUntilStep("decoding ran", () => didDecode);
96
96
}
97
97
98
+
[TestCase(false)]
99
+
[TestCase(true)]
100
+
public void TestDecodingStopsBeforeStartTime(bool looping)
101
+
{
102
+
AddStep("Set looping", () => video.Loop = looping);
103
+
104
+
AddStep("Jump back to before start time", () => clock.CurrentTime = -30000);
105
+
106
+
AddUntilStep("decoding stopped", () => video.State == VideoDecoder.DecoderState.Ready);
107
+
108
+
AddStep("reset decode state", () => didDecode = false);
109
+
110
+
AddWaitStep("wait a bit", 10);
111
+
AddAssert("decoding didn't run", () => !didDecode);
112
+
113
+
AddStep("seek close to start", () => clock.CurrentTime = -500);
114
+
AddUntilStep("decoding ran", () => didDecode);
115
+
}
116
+
98
117
[Test]
99
118
public void TestJumpForward()
100
119
{
-2
osu.Framework.iOS/IOSGameHost.cs
-2
osu.Framework.iOS/IOSGameHost.cs
···
107
107
108
108
public override Storage GetStorage(string path) => new IOSStorage(path, this);
109
109
110
-
public override string UserStoragePath => Environment.GetFolderPath(Environment.SpecialFolder.Personal);
111
-
112
110
public override void OpenFileExternally(string filename) => throw new NotImplementedException();
113
111
114
112
public override void OpenUrlExternally(string url)
+4
-3
osu.Framework/Graphics/Animations/AnimationClockComposite.cs
+4
-3
osu.Framework/Graphics/Animations/AnimationClockComposite.cs
···
83
83
{
84
84
get
85
85
{
86
-
if (Loop)
87
-
return manualClock.CurrentTime % Duration;
86
+
double current = manualClock.CurrentTime;
87
+
88
+
if (Loop) current %= Duration;
88
89
89
-
return Math.Min(manualClock.CurrentTime, Duration);
90
+
return Math.Clamp(current, 0, Duration);
90
91
}
91
92
set
92
93
{
+1
-1
osu.Framework/Graphics/Performance/FrameTimeDisplay.cs
+1
-1
osu.Framework/Graphics/Performance/FrameTimeDisplay.cs
···
99
99
elapsedSinceLastUpdate = 0;
100
100
101
101
counter.Text = $"{displayFps:0}fps ({rollingElapsed:0.00}ms)"
102
-
+ (clock.Throttling ? $"{(clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞").PadLeft(4)}hz" : string.Empty);
102
+
+ (clock.Throttling ? $"{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞").PadLeft(4)}hz" : string.Empty);
103
103
}
104
104
105
105
private class CounterText : SpriteText
+15
-11
osu.Framework/Graphics/Video/Video.cs
+15
-11
osu.Framework/Graphics/Video/Video.cs
···
124
124
seekIntoSync();
125
125
}
126
126
127
-
var nextFrame = availableFrames.Count > 0 ? availableFrames.Peek() : null;
127
+
var peekFrame = availableFrames.Count > 0 ? availableFrames.Peek() : null;
128
+
bool outOfSync = false;
128
129
129
-
if (nextFrame != null)
130
+
if (peekFrame != null)
130
131
{
131
-
bool tooFarBehind = Math.Abs(PlaybackPosition - nextFrame.Time) > lenience_before_seek &&
132
-
(!Loop ||
133
-
(Math.Abs(PlaybackPosition - decoder.Duration - nextFrame.Time) > lenience_before_seek &&
134
-
Math.Abs(PlaybackPosition + decoder.Duration - nextFrame.Time) > lenience_before_seek)
135
-
);
132
+
outOfSync = Math.Abs(PlaybackPosition - peekFrame.Time) > lenience_before_seek;
136
133
137
-
// we are too far ahead or too far behind
138
-
if (tooFarBehind && decoder.CanSeek)
134
+
if (Loop)
139
135
{
140
-
Logger.Log($"Video too far out of sync ({nextFrame.Time}), seeking to {PlaybackPosition}");
141
-
seekIntoSync();
136
+
// handle looping bounds (as we could be in the roll-over process between loops).
137
+
outOfSync &= Math.Abs(PlaybackPosition - decoder.Duration - peekFrame.Time) > lenience_before_seek &&
138
+
Math.Abs(PlaybackPosition + decoder.Duration - peekFrame.Time) > lenience_before_seek;
142
139
}
140
+
}
141
+
142
+
// we are too far ahead or too far behind
143
+
if (outOfSync && decoder.CanSeek)
144
+
{
145
+
Logger.Log($"Video too far out of sync ({peekFrame.Time}), seeking to {PlaybackPosition}");
146
+
seekIntoSync();
143
147
}
144
148
145
149
var frameTime = CurrentFrameTime;
+50
-2
osu.Framework/Platform/GameHost.cs
+50
-2
osu.Framework/Platform/GameHost.cs
···
136
136
/// <param name="path">The absolute path to be used as a root for the storage.</param>
137
137
public abstract Storage GetStorage(string path);
138
138
139
-
public abstract string UserStoragePath { get; }
139
+
/// <summary>
140
+
/// All valid user storage paths in order of usage priority.
141
+
/// </summary>
142
+
public virtual IEnumerable<string> UserStoragePaths => Environment.GetFolderPath(Environment.SpecialFolder.Personal).Yield();
140
143
141
144
/// <summary>
142
145
/// The main storage as proposed by the host game.
···
187
190
188
191
private double maximumUpdateHz;
189
192
193
+
/// <summary>
194
+
/// The target number of update frames per second when the game window is active.
195
+
/// </summary>
196
+
/// <remarks>
197
+
/// A value of 0 is treated the same as "unlimited" or <see cref="double.MaxValue"/>.
198
+
/// </remarks>
190
199
public double MaximumUpdateHz
191
200
{
192
201
get => maximumUpdateHz;
···
195
204
196
205
private double maximumDrawHz;
197
206
207
+
/// <summary>
208
+
/// The target number of draw frames per second when the game window is active.
209
+
/// </summary>
210
+
/// <remarks>
211
+
/// A value of 0 is treated the same as "unlimited" or <see cref="double.MaxValue"/>.
212
+
/// </remarks>
198
213
public double MaximumDrawHz
199
214
{
200
215
get => maximumDrawHz;
201
216
set => DrawThread.ActiveHz = maximumDrawHz = value;
202
217
}
203
218
219
+
/// <summary>
220
+
/// The target number of updates per second when the game window is inactive.
221
+
/// This is applied to all threads.
222
+
/// </summary>
223
+
/// <remarks>
224
+
/// A value of 0 is treated the same as "unlimited" or <see cref="double.MaxValue"/>.
225
+
/// </remarks>
204
226
public double MaximumInactiveHz
205
227
{
206
228
get => DrawThread.InactiveHz;
···
680
702
/// Finds the default <see cref="Storage"/> for the game to be used if <see cref="Game.CreateStorage"/> is not overridden.
681
703
/// </summary>
682
704
/// <returns>The <see cref="Storage"/>.</returns>
683
-
protected virtual Storage GetDefaultGameStorage() => GetStorage(UserStoragePath).GetStorageForDirectory(Name);
705
+
protected virtual Storage GetDefaultGameStorage()
706
+
{
707
+
// first check all valid paths for any existing install.
708
+
foreach (var path in UserStoragePaths)
709
+
{
710
+
var storage = GetStorage(path);
711
+
712
+
// if an existing data directory exists for this application, prefer it immediately.
713
+
if (storage.ExistsDirectory(Name))
714
+
return storage.GetStorageForDirectory(Name);
715
+
}
716
+
717
+
// if an existing directory could not be found, use the first path that can be created.
718
+
foreach (var path in UserStoragePaths)
719
+
{
720
+
try
721
+
{
722
+
return GetStorage(path).GetStorageForDirectory(Name);
723
+
}
724
+
catch
725
+
{
726
+
// may fail on directory creation.
727
+
}
728
+
}
729
+
730
+
throw new InvalidOperationException("No valid user storage path could be resolved.");
731
+
}
684
732
685
733
/// <summary>
686
734
/// Pauses all active threads. Call <see cref="Resume"/> to resume execution.
+17
-5
osu.Framework/Platform/HeadlessGameHost.cs
+17
-5
osu.Framework/Platform/HeadlessGameHost.cs
···
26
26
27
27
public override void OpenUrlExternally(string url) => Logger.Log($"Application has requested URL \"{url}\" to be opened.");
28
28
29
-
public override string UserStoragePath => "./headless/";
29
+
public override IEnumerable<string> UserStoragePaths => new[] { "./headless/" };
30
30
31
31
public HeadlessGameHost(string gameName = null, bool bindIPC = false, bool realtime = true, bool portableInstallation = false)
32
32
: base(gameName ?? Guid.NewGuid().ToString(), bindIPC, portableInstallation: portableInstallation)
···
51
51
{
52
52
base.SetupForRun();
53
53
54
-
MaximumDrawHz = double.MaxValue;
55
-
MaximumUpdateHz = double.MaxValue;
56
-
MaximumInactiveHz = double.MaxValue;
54
+
// We want the draw thread to run, but it doesn't matter how fast it runs.
55
+
// This limiting is mostly to reduce CPU overhead.
56
+
MaximumDrawHz = 60;
57
57
58
-
if (!realtime) customClock = new FramedClock(new FastClock(CLOCK_RATE));
58
+
if (!realtime)
59
+
{
60
+
customClock = new FramedClock(new FastClock(CLOCK_RATE));
61
+
62
+
// time is incremented per frame, rather than based on the real-world time.
63
+
// therefore our goal is to run frames as fast as possible.
64
+
MaximumUpdateHz = MaximumInactiveHz = 0;
65
+
}
66
+
else
67
+
{
68
+
// in realtime runs, set a sane upper limit to avoid cpu overhead from spinning.
69
+
MaximumUpdateHz = MaximumInactiveHz = 1000;
70
+
}
59
71
}
60
72
61
73
protected override void DrawFrame()
+8
-13
osu.Framework/Platform/Linux/LinuxGameHost.cs
+8
-13
osu.Framework/Platform/Linux/LinuxGameHost.cs
···
2
2
// See the LICENCE file in the repository root for full licence text.
3
3
4
4
using System;
5
+
using System.Collections.Generic;
5
6
using System.IO;
6
7
using osu.Framework.Platform.Linux.SDL2;
7
8
···
16
17
17
18
protected override IWindow CreateWindow() => new SDL2DesktopWindow();
18
19
19
-
public override string UserStoragePath
20
+
public override IEnumerable<string> UserStoragePaths
20
21
{
21
22
get
22
23
{
23
-
string home = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
24
24
string xdg = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
25
-
string[] paths =
26
-
{
27
-
xdg ?? Path.Combine(home, ".local", "share"),
28
-
Path.Combine(home)
29
-
};
25
+
26
+
if (!string.IsNullOrEmpty(xdg))
27
+
yield return xdg;
30
28
31
-
foreach (string path in paths)
32
-
{
33
-
if (Directory.Exists(path))
34
-
return path;
35
-
}
29
+
yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".local", "share");
36
30
37
-
return paths[0];
31
+
foreach (var path in base.UserStoragePaths)
32
+
yield return path;
38
33
}
39
34
}
40
35
+7
-13
osu.Framework/Platform/MacOS/MacOSGameHost.cs
+7
-13
osu.Framework/Platform/MacOS/MacOSGameHost.cs
···
22
22
23
23
protected override IWindow CreateWindow() => new MacOSWindow();
24
24
25
-
public override string UserStoragePath
25
+
public override IEnumerable<string> UserStoragePaths
26
26
{
27
27
get
28
28
{
29
-
string home = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
30
29
string xdg = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
31
-
string[] paths =
32
-
{
33
-
xdg ?? Path.Combine(home, ".local", "share"),
34
-
Path.Combine(home)
35
-
};
36
30
37
-
foreach (string path in paths)
38
-
{
39
-
if (Directory.Exists(path))
40
-
return path;
41
-
}
31
+
if (!string.IsNullOrEmpty(xdg))
32
+
yield return xdg;
42
33
43
-
return paths[0];
34
+
yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), ".local", "share");
35
+
36
+
foreach (var path in base.UserStoragePaths)
37
+
yield return path;
44
38
}
45
39
}
46
40
+4
-1
osu.Framework/Platform/Windows/WindowsGameHost.cs
+4
-1
osu.Framework/Platform/Windows/WindowsGameHost.cs
···
6
6
using System.Diagnostics;
7
7
using System.IO;
8
8
using System.Linq;
9
+
using osu.Framework.Extensions.IEnumerableExtensions;
9
10
using osu.Framework.Input;
10
11
using osu.Framework.Input.Bindings;
11
12
using osu.Framework.Input.Handlers;
···
20
21
21
22
public override Clipboard GetClipboard() => new WindowsClipboard();
22
23
23
-
public override string UserStoragePath => Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
24
+
public override IEnumerable<string> UserStoragePaths =>
25
+
// on windows this is guaranteed to exist (and be usable) so don't fallback to the base/default.
26
+
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData).Yield();
24
27
25
28
#if NET5_0
26
29
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
+1
-1
osu.Framework/Statistics/PerformanceMonitor.cs
+1
-1
osu.Framework/Statistics/PerformanceMonitor.cs
···
57
57
58
58
private Thread thread;
59
59
60
-
public double FrameAimTime => 1000.0 / (Clock?.MaximumUpdateHz ?? double.MaxValue);
60
+
public double FrameAimTime => 1000.0 / (Clock?.MaximumUpdateHz > 0 ? Clock.MaximumUpdateHz : double.MaxValue);
61
61
62
62
internal PerformanceMonitor(GameThread thread, IEnumerable<StatisticsCounterType> counters)
63
63
{
-4
osu.Framework/Testing/TestBrowserTestRunner.cs
-4
osu.Framework/Testing/TestBrowserTestRunner.cs
···
41
41
{
42
42
base.LoadComplete();
43
43
44
-
host.MaximumDrawHz = int.MaxValue;
45
-
host.MaximumUpdateHz = int.MaxValue;
46
-
host.MaximumInactiveHz = int.MaxValue;
47
-
48
44
AddInternal(browser);
49
45
50
46
Console.WriteLine($@"{(int)Time.Current}: Running {browser.TestTypes.Count} visual test cases...");
+4
-2
osu.Framework/Testing/TestRunHeadlessGameHost.cs
+4
-2
osu.Framework/Testing/TestRunHeadlessGameHost.cs
···
1
1
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2
2
// See the LICENCE file in the repository root for full licence text.
3
3
4
+
using System.Collections.Generic;
4
5
using System.IO;
6
+
using osu.Framework.Extensions.IEnumerableExtensions;
5
7
using osu.Framework.Logging;
6
8
using osu.Framework.Platform;
7
9
···
14
16
{
15
17
private readonly bool bypassCleanup;
16
18
17
-
public override string UserStoragePath { get; }
19
+
public override IEnumerable<string> UserStoragePaths { get; }
18
20
19
21
public static string TemporaryTestDirectory = Path.Combine(Path.GetTempPath(), "of-test-headless");
20
22
···
22
24
: base(name, bindIPC, realtime, portableInstallation)
23
25
{
24
26
this.bypassCleanup = bypassCleanup;
25
-
UserStoragePath = TemporaryTestDirectory;
27
+
UserStoragePaths = TemporaryTestDirectory.Yield();
26
28
}
27
29
28
30
protected override void Dispose(bool isDisposing)
-9
osu.Framework/Testing/TestSceneTestRunner.cs
-9
osu.Framework/Testing/TestSceneTestRunner.cs
···
58
58
if (volume != null) volume.Value = volumeAtStartup;
59
59
}
60
60
61
-
protected override void LoadComplete()
62
-
{
63
-
base.LoadComplete();
64
-
65
-
host.MaximumDrawHz = int.MaxValue;
66
-
host.MaximumUpdateHz = int.MaxValue;
67
-
host.MaximumInactiveHz = int.MaxValue;
68
-
}
69
-
70
61
/// <summary>
71
62
/// Blocks execution until a provided <see cref="TestScene"/> runs to completion.
72
63
/// </summary>
+8
-2
osu.Framework/Threading/GameThread.cs
+8
-2
osu.Framework/Threading/GameThread.cs
···
97
97
private CultureInfo culture;
98
98
99
99
/// <summary>
100
-
/// The refresh rate this thread should run at when active. Only applies when the underlying clock is throttling.
100
+
/// The target number of updates per second when the game window is active.
101
101
/// </summary>
102
+
/// <remarks>
103
+
/// A value of 0 is treated the same as "unlimited" or <see cref="double.MaxValue"/>.
104
+
/// </remarks>
102
105
public double ActiveHz
103
106
{
104
107
get => activeHz;
···
112
115
private double activeHz = DEFAULT_ACTIVE_HZ;
113
116
114
117
/// <summary>
115
-
/// The refresh rate this thread should run at when inactive. Only applies when the underlying clock is throttling.
118
+
/// The target number of updates per second when the game window is inactive.
116
119
/// </summary>
120
+
/// <remarks>
121
+
/// A value of 0 is treated the same as "unlimited" or <see cref="double.MaxValue"/>.
122
+
/// </remarks>
117
123
public double InactiveHz
118
124
{
119
125
get => inactiveHz;
+6
-3
osu.Framework/Timing/ThrottledFrameClock.cs
+6
-3
osu.Framework/Timing/ThrottledFrameClock.cs
···
15
15
public class ThrottledFrameClock : FramedClock
16
16
{
17
17
/// <summary>
18
-
/// The number of updated per second which is permitted.
18
+
/// The target number of updates per second. Only used when <see cref="Throttling"/> is <c>true</c>.
19
19
/// </summary>
20
+
/// <remarks>
21
+
/// A value of 0 is treated the same as "unlimited" or <see cref="double.MaxValue"/>.
22
+
/// </remarks>
20
23
public double MaximumUpdateHz = 1000.0;
21
24
22
25
/// <summary>
23
-
/// Whether throttling should be enabled. Defaults to true.
26
+
/// Whether throttling should be enabled. Defaults to <c>true</c>.
24
27
/// </summary>
25
28
public bool Throttling = true;
26
29
···
37
40
38
41
if (Throttling)
39
42
{
40
-
if (MaximumUpdateHz > 0)
43
+
if (MaximumUpdateHz > 0 && MaximumUpdateHz < double.MaxValue)
41
44
{
42
45
throttle();
43
46
}