diff --git a/.idea/.idea.osu.Android/.idea/.name b/.idea/.idea.osu.Android/.idea/.name
new file mode 100644
index 0000000000..86363b495c
--- /dev/null
+++ b/.idea/.idea.osu.Android/.idea/.name
@@ -0,0 +1 @@
+osu.Android
\ No newline at end of file
diff --git a/.idea/.idea.osu.Android/.idea/indexLayout.xml b/.idea/.idea.osu.Android/.idea/indexLayout.xml
new file mode 100644
index 0000000000..7b08163ceb
--- /dev/null
+++ b/.idea/.idea.osu.Android/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Android/.idea/misc.xml b/.idea/.idea.osu.Android/.idea/misc.xml
new file mode 100644
index 0000000000..1d8c84d0af
--- /dev/null
+++ b/.idea/.idea.osu.Android/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml
new file mode 100644
index 0000000000..4bb9f4d2a0
--- /dev/null
+++ b/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Android/.idea/vcs.xml b/.idea/.idea.osu.Android/.idea/vcs.xml
new file mode 100644
index 0000000000..94a25f7f4c
--- /dev/null
+++ b/.idea/.idea.osu.Android/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/misc.xml b/.idea/.idea.osu/.idea/misc.xml
new file mode 100644
index 0000000000..1d8c84d0af
--- /dev/null
+++ b/.idea/.idea.osu/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/modules.xml b/.idea/.idea.osu/.idea/modules.xml
deleted file mode 100644
index 0360fdbc5e..0000000000
--- a/.idea/.idea.osu/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu/.idea/projectSettingsUpdater.xml
index 7515e76054..4bb9f4d2a0 100644
--- a/.idea/.idea.osu/.idea/projectSettingsUpdater.xml
+++ b/.idea/.idea.osu/.idea/projectSettingsUpdater.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt
index c567adc0ae..e96ad48325 100644
--- a/CodeAnalysis/BannedSymbols.txt
+++ b/CodeAnalysis/BannedSymbols.txt
@@ -13,3 +13,5 @@ M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.H
M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection,NotificationCallbackDelegate) instead.
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable,NotificationCallbackDelegate) instead.
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead.
+M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
+P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
diff --git a/README.md b/README.md
index f18c5e76f9..b1dfcab416 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi
**Latest build:**
-| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
+| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.15+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- | ------------- |
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs
index 536fdfc6df..5973db908c 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Platform;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
@@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
public class TestSceneOsuGame : OsuTestScene
{
[BackgroundDependencyLoader]
- private void load(GameHost host, OsuGameBase gameBase)
+ private void load()
{
Children = new Drawable[]
{
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
index 4f810ce17f..03ee7c9204 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
index 3cdf44e6f1..b75a5ec187 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Platform;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
@@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
public class TestSceneOsuGame : OsuTestScene
{
[BackgroundDependencyLoader]
- private void load(GameHost host, OsuGameBase gameBase)
+ private void load()
{
Children = new Drawable[]
{
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
index fd6bd9b714..55c0cf6a3b 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs
index 4d3f5086d9..ffe921b54c 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Platform;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
@@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
public class TestSceneOsuGame : OsuTestScene
{
[BackgroundDependencyLoader]
- private void load(GameHost host, OsuGameBase gameBase)
+ private void load()
{
Children = new Drawable[]
{
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
index 65cfb2bff4..b45505678c 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
index 3cdf44e6f1..b75a5ec187 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Platform;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
@@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
public class TestSceneOsuGame : OsuTestScene
{
[BackgroundDependencyLoader]
- private void load(GameHost host, OsuGameBase gameBase)
+ private void load()
{
Children = new Drawable[]
{
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
index fd6bd9b714..55c0cf6a3b 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
[STAThread]
public static int Main(string[] args)
{
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
{
host.Run(new OsuTestBrowser());
return 0;
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs
index 0e50030162..ab8c6bb2e9 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs
@@ -7,7 +7,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Pippidon.UI
private PippidonCharacter pippidon;
[BackgroundDependencyLoader]
- private void load(TextureStore textures)
+ private void load()
{
AddRangeInternal(new Drawable[]
{
diff --git a/osu.Android.props b/osu.Android.props
index 67a9cd41dd..2902c74f0a 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,11 +51,11 @@
-
-
+
+
-
+
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index b234207848..cd3fb7eb61 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -10,14 +10,11 @@ using System.Runtime.Versioning;
using System.Threading.Tasks;
using Microsoft.Win32;
using osu.Desktop.Security;
-using osu.Desktop.Overlays;
using osu.Framework.Platform;
using osu.Game;
using osu.Desktop.Updater;
using osu.Framework;
using osu.Framework.Logging;
-using osu.Framework.Screens;
-using osu.Game.Screens.Menu;
using osu.Game.Updater;
using osu.Desktop.Windows;
using osu.Framework.Threading;
@@ -27,13 +24,9 @@ namespace osu.Desktop
{
internal class OsuGameDesktop : OsuGame
{
- private readonly bool noVersionOverlay;
- private VersionManager versionManager;
-
public OsuGameDesktop(string[] args = null)
: base(args)
{
- noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false;
}
public override StableStorage GetStorageForStableInstall()
@@ -114,9 +107,6 @@ namespace osu.Desktop
{
base.LoadComplete();
- if (!noVersionOverlay)
- LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add);
-
LoadComponentAsync(new DiscordRichPresence(), Add);
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
@@ -125,23 +115,6 @@ namespace osu.Desktop
LoadComponentAsync(new ElevatedPrivilegesChecker(), Add);
}
- protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
- {
- base.ScreenChanged(lastScreen, newScreen);
-
- switch (newScreen)
- {
- case IntroScreen _:
- case MainMenu _:
- versionManager?.Show();
- break;
-
- default:
- versionManager?.Hide();
- break;
- }
- }
-
public override void SetHost(GameHost host)
{
base.SetHost(host);
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 7ec7d53a7e..b944068e78 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -55,7 +55,7 @@ namespace osu.Desktop
}
}
- using (DesktopGameHost host = Host.GetSuitableHost(gameName, true))
+ using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = true }))
{
host.ExceptionThrown += handleException;
diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs
index 62ea3e0399..8f3ad853dc 100644
--- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs
+++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs
@@ -73,7 +73,7 @@ namespace osu.Desktop.Security
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, NotificationOverlay notificationOverlay)
+ private void load(OsuColour colours)
{
Icon = FontAwesome.Solid.ShieldAlt;
IconBackground.Colour = colours.YellowDark;
diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs
index f19d741107..fdca2028d3 100644
--- a/osu.Desktop/Windows/WindowsKey.cs
+++ b/osu.Desktop/Windows/WindowsKey.cs
@@ -4,6 +4,8 @@
using System;
using System.Runtime.InteropServices;
+// ReSharper disable IdentifierTypo
+
namespace osu.Desktop.Windows
{
internal class WindowsKey
diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs
new file mode 100644
index 0000000000..bf9467700c
--- /dev/null
+++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs
@@ -0,0 +1,141 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using System.Threading;
+using BenchmarkDotNet.Attributes;
+using osu.Framework.Testing;
+using osu.Framework.Threading;
+using osu.Game.Beatmaps;
+using osu.Game.Database;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Tests.Resources;
+
+namespace osu.Game.Benchmarks
+{
+ public class BenchmarkRealmReads : BenchmarkTest
+ {
+ private TemporaryNativeStorage storage;
+ private RealmAccess realm;
+ private UpdateThread updateThread;
+
+ [Params(1, 100, 1000)]
+ public int ReadsPerFetch { get; set; }
+
+ public override void SetUp()
+ {
+ storage = new TemporaryNativeStorage("realm-benchmark");
+ storage.DeleteDirectory(string.Empty);
+
+ realm = new RealmAccess(storage, "client");
+
+ realm.Run(r =>
+ {
+ realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo })));
+ });
+
+ updateThread = new UpdateThread(() => { }, null);
+ updateThread.Start();
+ }
+
+ [Benchmark]
+ public void BenchmarkDirectPropertyRead()
+ {
+ realm.Run(r =>
+ {
+ var beatmapSet = r.All().First();
+
+ for (int i = 0; i < ReadsPerFetch; i++)
+ {
+ string _ = beatmapSet.Beatmaps.First().Hash;
+ }
+ });
+ }
+
+ [Benchmark]
+ public void BenchmarkDirectPropertyReadUpdateThread()
+ {
+ var done = new ManualResetEventSlim();
+
+ updateThread.Scheduler.Add(() =>
+ {
+ try
+ {
+ var beatmapSet = realm.Realm.All().First();
+
+ for (int i = 0; i < ReadsPerFetch; i++)
+ {
+ string _ = beatmapSet.Beatmaps.First().Hash;
+ }
+ }
+ finally
+ {
+ done.Set();
+ }
+ });
+
+ done.Wait();
+ }
+
+ [Benchmark]
+ public void BenchmarkRealmLivePropertyRead()
+ {
+ realm.Run(r =>
+ {
+ var beatmapSet = r.All().First().ToLive(realm);
+
+ for (int i = 0; i < ReadsPerFetch; i++)
+ {
+ string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash);
+ }
+ });
+ }
+
+ [Benchmark]
+ public void BenchmarkRealmLivePropertyReadUpdateThread()
+ {
+ var done = new ManualResetEventSlim();
+
+ updateThread.Scheduler.Add(() =>
+ {
+ try
+ {
+ var beatmapSet = realm.Realm.All().First().ToLive(realm);
+
+ for (int i = 0; i < ReadsPerFetch; i++)
+ {
+ string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash);
+ }
+ }
+ finally
+ {
+ done.Set();
+ }
+ });
+
+ done.Wait();
+ }
+
+ [Benchmark]
+ public void BenchmarkDetachedPropertyRead()
+ {
+ realm.Run(r =>
+ {
+ var beatmapSet = r.All().First().Detach();
+
+ for (int i = 0; i < ReadsPerFetch; i++)
+ {
+ string _ = beatmapSet.Beatmaps.First().Hash;
+ }
+ });
+ }
+
+ [GlobalCleanup]
+ public void Cleanup()
+ {
+ realm?.Dispose();
+ storage?.Dispose();
+ updateThread?.Exit();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index be1885cfa6..baca8166d1 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -14,7 +14,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- [Timeout(10000)]
public class CatchBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
index d4c2c0f0af..e345e03c96 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
@@ -29,7 +29,13 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
protected CatchSelectionBlueprintTestScene()
{
- EditorBeatmap = new EditorBeatmap(new CatchBeatmap()) { Difficulty = { CircleSize = 0 } };
+ EditorBeatmap = new EditorBeatmap(new CatchBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new CatchRuleset().RulesetInfo,
+ }
+ }) { Difficulty = { CircleSize = 0 } };
EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint
{
BeatLength = 100
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index f552c3c27b..1014158fc1 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 },
+ Difficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 },
Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
index e89a95ae37..96ac5c4bf2 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
+ Difficulty = new BeatmapDifficulty { CircleSize = 6 },
Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
index 1ff31697b8..0a4ef49e19 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
+ Difficulty = new BeatmapDifficulty { CircleSize = 6 },
Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index 23f6222eb6..4b8fede369 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -35,12 +35,12 @@ namespace osu.Game.Rulesets.Catch.Tests
HitObjects = new List { new Fruit() },
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty(),
+ Difficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
Title = @"You're breathtaking",
- AuthorString = @"Everyone",
+ Author = { Username = @"Everyone" },
},
Ruleset = new CatchRuleset().RulesetInfo
},
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index 163fee49fb..a5b44dc605 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.Tests
BeatmapInfo =
{
Ruleset = ruleset,
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.6f }
+ Difficulty = new BeatmapDifficulty { CircleSize = 3.6f }
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
index 269e783899..4601234669 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 5, SliderMultiplier = 2 },
+ Difficulty = new BeatmapDifficulty { CircleSize = 5, SliderMultiplier = 2 },
Ruleset = ruleset
},
HitObjects = new List
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
index f399f48ebd..2d92c925d7 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
@@ -3,6 +3,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
@@ -15,9 +16,26 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override double ScoreMultiplier => 1.12;
- private const float default_flashlight_size = 350;
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableNumber SizeMultiplier { get; } = new BindableNumber
+ {
+ MinValue = 0.5f,
+ MaxValue = 1.5f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
- public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield);
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
+ public override float DefaultFlashlightSize => 350;
+
+ protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
private CatchPlayfield playfield;
@@ -31,10 +49,11 @@ namespace osu.Game.Rulesets.Catch.Mods
{
private readonly CatchPlayfield playfield;
- public CatchFlashlight(CatchPlayfield playfield)
+ public CatchFlashlight(CatchModFlashlight modFlashlight, CatchPlayfield playfield)
+ : base(modFlashlight)
{
this.playfield = playfield;
- FlashlightSize = new Vector2(0, getSizeFor(0));
+ FlashlightSize = new Vector2(0, GetSizeFor(0));
}
protected override void Update()
@@ -44,19 +63,9 @@ namespace osu.Game.Rulesets.Catch.Mods
FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this);
}
- private float getSizeFor(int combo)
- {
- if (combo > 200)
- return default_flashlight_size * 0.8f;
- else if (combo > 100)
- return default_flashlight_size * 0.9f;
- else
- return default_flashlight_size;
- }
-
protected override void OnComboChange(ValueChangedEvent e)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
index 5ccb191a9b..50be13c4e0 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
@@ -29,7 +29,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo();
[Cached(typeof(EditorBeatmap))]
- private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition()));
+ private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition())
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new ManiaRuleset().RulesetInfo
+ }
+ });
private readonly ManiaBeatSnapGrid beatSnapGrid;
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
index a30e09cd29..5dd7c23ab6 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
@@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
AddStep("setup compose screen", () =>
{
- var editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }))
+ var editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
- };
+ });
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
index 01d80881fa..9788dfe844 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
@@ -203,10 +203,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
InternalChildren = new Drawable[]
{
- EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }))
+ EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }
- },
+ }),
Composer = new ManiaHitObjectComposer(new ManiaRuleset())
};
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 948f088b4e..837474ad9e 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -14,7 +14,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
- [Timeout(10000)]
public class ManiaBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
index 36765d61bf..9c987efc60 100644
--- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
+++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
@@ -4,11 +4,14 @@ Version: 2.5
[Mania]
Keys: 4
ColumnLineWidth: 3,1,3,1,1
-Hit0: mania/hit0
-Hit50: mania/hit50
-Hit100: mania/hit100
-Hit200: mania/hit200
-Hit300: mania/hit300
-Hit300g: mania/hit300g
+// some skins found in the wild had configuration keys where the @2x suffix was included in the values.
+// the expected compatibility behaviour is that the presence of the @2x suffix shouldn't change anything
+// if @2x assets are present.
+Hit0: mania/hit0@2x
+Hit50: mania/hit50@2x
+Hit100: mania/hit100@2x
+Hit200: mania/hit200@2x
+Hit300: mania/hit300@2x
+Hit300g: mania/hit300g@2x
StageLeft: mania/stage-left
StageRight: mania/stage-right
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
index 75a5495078..d033676ec7 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
@@ -5,8 +5,10 @@ using System;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Mania.Skinning.Legacy;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
@@ -23,15 +25,24 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
if (hitWindows.IsHitResultAllowed(result))
{
- AddStep("Show " + result.GetDescription(), () => SetContents(_ =>
- new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
- {
- Type = result
- }, null)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- }));
+ AddStep("Show " + result.GetDescription(), () =>
+ {
+ SetContents(_ =>
+ new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
+ {
+ Type = result
+ }, null)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+
+ // for test purposes, undo the Y adjustment related to the `ScorePosition` legacy positioning config value
+ // (see `LegacyManiaJudgementPiece.load()`).
+ // this prevents the judgements showing somewhere below or above the bounding box of the judgement.
+ foreach (var legacyPiece in this.ChildrenOfType())
+ legacyPiece.Y = 0;
+ });
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
index 4387bc6b3b..f973cb5ed3 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Mania.Tests
},
BeatmapInfo =
{
- BaseDifficulty = new BeatmapDifficulty
+ Difficulty = new BeatmapDifficulty
{
SliderTickRate = 4,
OverallDifficulty = 10,
@@ -306,7 +306,7 @@ namespace osu.Game.Rulesets.Mania.Tests
},
BeatmapInfo =
{
- BaseDifficulty = new BeatmapDifficulty { SliderTickRate = tick_rate },
+ Difficulty = new BeatmapDifficulty { SliderTickRate = tick_rate },
Ruleset = new ManiaRuleset().RulesetInfo
},
};
@@ -383,7 +383,7 @@ namespace osu.Game.Rulesets.Mania.Tests
},
BeatmapInfo =
{
- BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 },
+ Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
Ruleset = new ManiaRuleset().RulesetInfo
},
};
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 9d0aaec2ba..47e0e6d7b1 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
public static int GetColumnCountForNonConvert(BeatmapInfo beatmapInfo)
{
- double roundedCircleSize = Math.Round(beatmapInfo.BaseDifficulty.CircleSize);
+ double roundedCircleSize = Math.Round(beatmapInfo.Difficulty.CircleSize);
return (int)Math.Max(1, roundedCircleSize);
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
index 5259fcbd5f..35889aea0c 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
@@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
}
[BackgroundDependencyLoader]
- private void load(IScrollingInfo scrollingInfo)
+ private void load()
{
InternalChildren = new Drawable[]
{
diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
index 0290230490..c8832dfdfb 100644
--- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
+++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania
public bool Matches(BeatmapInfo beatmapInfo)
{
- return !keys.HasFilter || (beatmapInfo.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
+ return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
}
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index b0e7545d3e..6fc7dc018b 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
- public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime, 0.5);
+ public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new ManiaHealthProcessor(drainStartTime, 0.5);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
index 86a00271e9..1ee4ea12e3 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Layout;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
using osuTK;
@@ -16,17 +17,35 @@ namespace osu.Game.Rulesets.Mania.Mods
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
- private const float default_flashlight_size = 180;
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableNumber SizeMultiplier { get; } = new BindableNumber
+ {
+ MinValue = 0.5f,
+ MaxValue = 3f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
- public override Flashlight CreateFlashlight() => new ManiaFlashlight();
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = false,
+ Value = false
+ };
+
+ public override float DefaultFlashlightSize => 50;
+
+ protected override Flashlight CreateFlashlight() => new ManiaFlashlight(this);
private class ManiaFlashlight : Flashlight
{
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
- public ManiaFlashlight()
+ public ManiaFlashlight(ManiaModFlashlight modFlashlight)
+ : base(modFlashlight)
{
- FlashlightSize = new Vector2(0, default_flashlight_size);
+ FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0));
AddLayout(flashlightProperties);
}
@@ -46,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Mods
protected override void OnComboChange(ValueChangedEvent e)
{
+ this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "RectangularFlashlight";
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs
new file mode 100644
index 0000000000..57c2ba9c6d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Scoring
+{
+ public class ManiaHealthProcessor : DrainingHealthProcessor
+ {
+ ///
+ public ManiaHealthProcessor(double drainStartTime, double drainLenience = 0)
+ : base(drainStartTime, drainLenience)
+ {
+ }
+
+ protected override HitResult GetSimulatedHitResult(Judgement judgement)
+ {
+ // Users are not expected to attain perfect judgements for all notes due to the tighter hit window.
+ return judgement.MaxResult == HitResult.Perfect ? HitResult.Great : judgement.MaxResult;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs
index 787807a8ea..1f3d4297f1 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs
@@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
var beatmap = new Beatmap
{
HitObjects = hitObjects,
- BeatmapInfo = new BeatmapInfo { BaseDifficulty = new BeatmapDifficulty(beatmapDifficulty) }
+ BeatmapInfo = new BeatmapInfo { Difficulty = new BeatmapDifficulty(beatmapDifficulty) }
};
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
index ef43c3a696..c770e2d96f 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
@@ -40,7 +40,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public TestSceneOsuDistanceSnapGrid()
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo
+ }
+ });
}
[SetUp]
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs
new file mode 100644
index 0000000000..b43b2b1461
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs
@@ -0,0 +1,225 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Input.Events;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Input.Bindings;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Edit;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Screens.Edit;
+using osu.Game.Screens.Edit.Compose.Components;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Tests.Editor
+{
+ public class TestSceneSliderSnapping : EditorTestScene
+ {
+ private const double beat_length = 1000;
+
+ protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
+ {
+ var controlPointInfo = new ControlPointInfo();
+ controlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
+ return new TestBeatmap(ruleset, false)
+ {
+ ControlPointInfo = controlPointInfo
+ };
+ }
+
+ private Slider slider;
+
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddStep("add unsnapped slider", () => EditorBeatmap.Add(slider = new Slider
+ {
+ StartTime = 0,
+ Position = OsuPlayfield.BASE_SIZE / 5,
+ Path = new SliderPath
+ {
+ ControlPoints =
+ {
+ new PathControlPoint(Vector2.Zero),
+ new PathControlPoint(OsuPlayfield.BASE_SIZE * 2 / 5),
+ new PathControlPoint(OsuPlayfield.BASE_SIZE * 3 / 5)
+ }
+ }
+ }));
+ AddStep("set beat divisor to 1/1", () =>
+ {
+ var beatDivisor = (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor));
+ beatDivisor.Value = 1;
+ });
+ }
+
+ [Test]
+ public void TestMovingUnsnappedSliderNodesSnaps()
+ {
+ PathControlPointPiece sliderEnd = null;
+
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("select slider end", () =>
+ {
+ sliderEnd = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last());
+ InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre);
+ });
+ AddStep("move slider end", () =>
+ {
+ InputManager.PressButton(MouseButton.Left);
+ InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre - new Vector2(0, 20));
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+ assertSliderSnapped(true);
+ }
+
+ [Test]
+ public void TestAddingControlPointToUnsnappedSliderNodesSnaps()
+ {
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("move mouse to new point location", () =>
+ {
+ var firstPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
+ var secondPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
+ InputManager.MoveMouseTo((firstPiece.ScreenSpaceDrawQuad.Centre + secondPiece.ScreenSpaceDrawQuad.Centre) / 2);
+ });
+ AddStep("move slider end", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Click(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+ assertSliderSnapped(true);
+ }
+
+ [Test]
+ public void TestRemovingControlPointFromUnsnappedSliderNodesSnaps()
+ {
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("move mouse to second control point", () =>
+ {
+ var secondPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
+ InputManager.MoveMouseTo(secondPiece);
+ });
+ AddStep("quick delete", () =>
+ {
+ InputManager.PressKey(Key.ShiftLeft);
+ InputManager.PressButton(MouseButton.Right);
+ InputManager.ReleaseKey(Key.ShiftLeft);
+ });
+ assertSliderSnapped(true);
+ }
+
+ [Test]
+ public void TestResizingUnsnappedSliderSnaps()
+ {
+ SelectionBoxScaleHandle handle = null;
+
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("move mouse to scale handle", () =>
+ {
+ handle = this.ChildrenOfType().First();
+ InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
+ });
+ AddStep("scale slider", () =>
+ {
+ InputManager.PressButton(MouseButton.Left);
+ InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre + new Vector2(20, 20));
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+ assertSliderSnapped(true);
+ }
+
+ [Test]
+ public void TestRotatingUnsnappedSliderDoesNotSnap()
+ {
+ SelectionBoxRotationHandle handle = null;
+
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("move mouse to rotate handle", () =>
+ {
+ handle = this.ChildrenOfType().First();
+ InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
+ });
+ AddStep("scale slider", () =>
+ {
+ InputManager.PressButton(MouseButton.Left);
+ InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre + new Vector2(0, 20));
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+ assertSliderSnapped(false);
+ }
+
+ [Test]
+ public void TestFlippingSliderDoesNotSnap()
+ {
+ OsuSelectionHandler selectionHandler = null;
+
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("flip slider horizontally", () =>
+ {
+ selectionHandler = this.ChildrenOfType().Single();
+ selectionHandler.OnPressed(new KeyBindingPressEvent(InputManager.CurrentState, GlobalAction.EditorFlipHorizontally));
+ });
+
+ assertSliderSnapped(false);
+
+ AddStep("flip slider vertically", () =>
+ {
+ selectionHandler = this.ChildrenOfType().Single();
+ selectionHandler.OnPressed(new KeyBindingPressEvent(InputManager.CurrentState, GlobalAction.EditorFlipVertically));
+ });
+
+ assertSliderSnapped(false);
+ }
+
+ [Test]
+ public void TestReversingSliderDoesNotSnap()
+ {
+ assertSliderSnapped(false);
+
+ AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
+ AddStep("reverse slider", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Key(Key.G);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+
+ assertSliderSnapped(false);
+ }
+
+ private void assertSliderSnapped(bool snapped)
+ => AddAssert($"slider is {(snapped ? "" : "not ")}snapped", () =>
+ {
+ double durationInBeatLengths = slider.Duration / beat_length;
+ double fractionalPart = durationInBeatLengths - (int)durationInBeatLengths;
+ return Precision.AlmostEquals(fractionalPart, 0) == snapped;
+ });
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs
new file mode 100644
index 0000000000..4750c97566
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs
@@ -0,0 +1,98 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Input;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Edit;
+using osu.Game.Screens.Edit.Compose.Components.Timeline;
+using osu.Game.Screens.Edit.Timing;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Tests.Editor
+{
+ public class TestSceneSliderVelocityAdjust : OsuGameTestScene
+ {
+ private Screens.Edit.Editor editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor;
+
+ private EditorBeatmap editorBeatmap => editor.ChildrenOfType().FirstOrDefault();
+
+ private EditorClock editorClock => editor.ChildrenOfType().FirstOrDefault();
+
+ private Slider slider => editorBeatmap.HitObjects.OfType().FirstOrDefault();
+
+ private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault();
+
+ private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType().First();
+
+ private IndeterminateSliderWithTextBoxInput velocityTextBox => Game.ChildrenOfType().First().ChildrenOfType>().First();
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
+
+ private bool editorComponentsReady => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true
+ && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true
+ && editor?.ChildrenOfType().FirstOrDefault()?.IsLoaded == true;
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestVelocityChangeSavesCorrectly(bool adjustVelocity)
+ {
+ double? velocity = null;
+
+ AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader()));
+ AddUntilStep("wait for editor load", () => editorComponentsReady);
+
+ AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time));
+ AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3));
+
+ AddStep("move mouse to centre", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre));
+ AddStep("start placement", () => InputManager.Click(MouseButton.Left));
+
+ AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
+ AddStep("end placement", () => InputManager.Click(MouseButton.Right));
+
+ AddStep("exit placement mode", () => InputManager.Key(Key.Number1));
+
+ AddAssert("slider placed", () => slider != null);
+
+ AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider));
+
+ AddAssert("ensure one slider placed", () => slider != null);
+
+ AddStep("store velocity", () => velocity = slider.Velocity);
+
+ if (adjustVelocity)
+ {
+ AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick());
+ AddStep("change velocity", () => velocityTextBox.Current.Value = 2);
+
+ AddAssert("velocity adjusted", () =>
+ {
+ Debug.Assert(velocity != null);
+ return Precision.AlmostEquals(velocity.Value * 2, slider.Velocity);
+ });
+
+ AddStep("store velocity", () => velocity = slider.Velocity);
+ }
+
+ AddStep("save", () => InputManager.Keys(PlatformAction.Save));
+ AddStep("exit", () => InputManager.Key(Key.Escape));
+
+ AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader()));
+ AddUntilStep("wait for editor load", () => editorComponentsReady);
+
+ AddStep("seek to slider", () => editorClock.Seek(slider.StartTime));
+ AddAssert("slider has correct velocity", () => slider.Velocity == velocity);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
index db8546c71b..9d06ff5801 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty
+ Difficulty = new BeatmapDifficulty
{
CircleSize = 8
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
index 8e226c7ded..44404ca245 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
@@ -145,6 +145,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private bool isBreak() => Player.IsBreakTime.Value;
- private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha);
+ private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f);
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index 5f44e1b6b6..4c11efcc7c 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -12,7 +12,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- [Timeout(10000)]
public class OsuBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
index 8cf29ddfbf..4e17c4c363 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
+ Difficulty = new BeatmapDifficulty { CircleSize = 6 },
Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs
index ef05bcd320..5e92bac986 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 10 },
+ Difficulty = new BeatmapDifficulty { OverallDifficulty = 10 },
Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
index f3392724ec..2368cc7365 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
@@ -358,7 +358,7 @@ namespace osu.Game.Rulesets.Osu.Tests
},
BeatmapInfo =
{
- BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
+ Difficulty = new BeatmapDifficulty { SliderTickRate = 3 },
Ruleset = new OsuRuleset().RulesetInfo
},
});
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs
index 2d43e1b95e..53fa3624b8 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs
@@ -364,7 +364,7 @@ namespace osu.Game.Rulesets.Osu.Tests
HitObjects = hitObjects,
BeatmapInfo =
{
- BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
+ Difficulty = new BeatmapDifficulty { SliderTickRate = 3 },
Ruleset = new OsuRuleset().RulesetInfo
},
});
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index d7d294df47..604ab73454 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private int countMeh;
private int countMiss;
- private int effectiveMissCount;
+ private double effectiveMissCount;
public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
: base(ruleset, attributes, score)
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(m => m is OsuModNoFail))
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
- if (mods.Any(m => m is OsuModSpunOut))
+ if (mods.Any(m => m is OsuModSpunOut) && totalHits > 0)
multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85);
if (mods.Any(h => h is OsuModRelax))
@@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount);
+ aimValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), effectiveMissCount);
aimValue *= getComboScalingFactor();
@@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
+ speedValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
speedValue *= getComboScalingFactor();
@@ -228,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)
- flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
+ flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
flashlightValue *= getComboScalingFactor();
@@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return flashlightValue;
}
- private int calculateEffectiveMissCount()
+ private double calculateEffectiveMissCount()
{
// Guess the number of misses + slider breaks from combo
double comboBasedMissCount = 0.0;
@@ -256,10 +256,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
}
- // Clamp misscount since it's derived from combo and can be higher than total hits and that breaks some calculations
+ // Clamp miss count since it's derived from combo and can be higher than total hits and that breaks some calculations
comboBasedMissCount = Math.Min(comboBasedMissCount, totalHits);
- return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount));
+ return Math.Max(countMiss, comboBasedMissCount);
}
private double getComboScalingFactor() => Attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index 065d4737a5..ae4141073e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -283,6 +283,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
}
}
+ // Snap the path to the current beat divisor before checking length validity.
+ slider.SnapTo(snapProvider);
+
if (!slider.Path.HasValidLength)
{
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
@@ -290,6 +293,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
slider.Position = oldPosition;
slider.StartTime = oldStartTime;
+ // Snap the path length again to undo the invalid length.
+ slider.SnapTo(snapProvider);
return;
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 07b6a1bdc2..b868c9a7ee 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
InternalChildren = new Drawable[]
{
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 2aebe05c2f..6cf2a493a9 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
controlPoints.BindTo(HitObject.Path.ControlPoints);
pathVersion.BindTo(HitObject.Path.Version);
- pathVersion.BindValueChanged(_ => updatePath());
+ pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject));
BodyPiece.UpdateFrom(HitObject);
}
@@ -208,6 +208,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
// Move the control points from the insertion index onwards to make room for the insertion
controlPoints.Insert(insertionIndex, pathControlPoint);
+ HitObject.SnapTo(composer);
+
return pathControlPoint;
}
@@ -227,7 +229,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
controlPoints.Remove(c);
}
- // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
+ // Snap the slider to the current beat divisor before checking length validity.
+ HitObject.SnapTo(composer);
+
+ // If there are 0 or 1 remaining control points, or the slider has an invalid length, it is in a degenerate form and should be deleted
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
{
placementHandler?.Delete(HitObject);
@@ -242,12 +247,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
HitObject.Position += first;
}
- private void updatePath()
- {
- HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
- editorBeatmap?.Update(HitObject);
- }
-
private void convertToStream()
{
if (editorBeatmap == null || changeHandler == null || beatDivisor == null)
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index 071ecf6329..efbac5439c 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -1,12 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+#nullable enable
+
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Utils;
using osu.Game.Extensions;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
@@ -18,6 +22,9 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuSelectionHandler : EditorSelectionHandler
{
+ [Resolved(CanBeNull = true)]
+ private IPositionSnapProvider? positionSnapProvider { get; set; }
+
///
/// During a transform, the initial origin is stored so it can be used throughout the operation.
///
@@ -27,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit
/// During a transform, the initial path types of a single selected slider are stored so they
/// can be maintained throughout the operation.
///
- private List referencePathTypes;
+ private List? referencePathTypes;
protected override void OnSelectionChanged()
{
@@ -197,6 +204,10 @@ namespace osu.Game.Rulesets.Osu.Edit
for (int i = 0; i < slider.Path.ControlPoints.Count; ++i)
slider.Path.ControlPoints[i].Type = referencePathTypes[i];
+ // Snap the slider's length to the current beat divisor
+ // to calculate the final resulting duration / bounding box before the final checks.
+ slider.SnapTo(positionSnapProvider);
+
//if sliderhead or sliderend end up outside playfield, revert scaling.
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
@@ -206,6 +217,9 @@ namespace osu.Game.Rulesets.Osu.Edit
foreach (var point in slider.Path.ControlPoints)
point.Position = oldControlPoints.Dequeue();
+
+ // Snap the slider's length again to undo the potentially-invalid length applied by the previous snap.
+ slider.SnapTo(positionSnapProvider);
}
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
index 300a9d48aa..b4eff57c55 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
@@ -12,7 +12,6 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Osu.Mods
@@ -21,27 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override double ScoreMultiplier => 1.12;
- private const float default_flashlight_size = 180;
-
private const double default_follow_delay = 120;
- private OsuFlashlight flashlight;
-
- public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight();
-
- public void ApplyToDrawableHitObject(DrawableHitObject drawable)
- {
- if (drawable is DrawableSlider s)
- s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
- }
-
- public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
- {
- base.ApplyToDrawableRuleset(drawableRuleset);
-
- flashlight.FollowDelay = FollowDelay.Value;
- }
-
[SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")]
public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay)
{
@@ -50,13 +30,45 @@ namespace osu.Game.Rulesets.Osu.Mods
Precision = default_follow_delay,
};
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableNumber SizeMultiplier { get; } = new BindableNumber
+ {
+ MinValue = 0.5f,
+ MaxValue = 2f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
+
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
+ public override float DefaultFlashlightSize => 180;
+
+ private OsuFlashlight flashlight;
+
+ protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this);
+
+ public void ApplyToDrawableHitObject(DrawableHitObject drawable)
+ {
+ if (drawable is DrawableSlider s)
+ s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange;
+ }
+
private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition
{
- public double FollowDelay { private get; set; }
+ private readonly double followDelay;
- public OsuFlashlight()
+ public OsuFlashlight(OsuModFlashlight modFlashlight)
+ : base(modFlashlight)
{
- FlashlightSize = new Vector2(0, getSizeFor(0));
+ followDelay = modFlashlight.FollowDelay.Value;
+
+ FlashlightSize = new Vector2(0, GetSizeFor(0));
}
public void OnSliderTrackingChange(ValueChangedEvent e)
@@ -71,24 +83,14 @@ namespace osu.Game.Rulesets.Osu.Mods
var destination = e.MousePosition;
FlashlightPosition = Interpolation.ValueAt(
- Math.Min(Math.Abs(Clock.ElapsedFrameTime), FollowDelay), position, destination, 0, FollowDelay, Easing.Out);
+ Math.Min(Math.Abs(Clock.ElapsedFrameTime), followDelay), position, destination, 0, followDelay, Easing.Out);
return base.OnMouseMove(e);
}
- private float getSizeFor(int combo)
- {
- if (combo > 200)
- return default_flashlight_size * 0.8f;
- else if (combo > 100)
- return default_flashlight_size * 0.9f;
- else
- return default_flashlight_size;
- }
-
protected override void OnComboChange(ValueChangedEvent e)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index ec87d3bfdf..c6db02ee02 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -10,7 +10,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@@ -69,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 0ad8e4ea68..1eddfb7fef 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -65,8 +65,8 @@ namespace osu.Game.Rulesets.Osu.Objects
double startTime = StartTime + (float)(i + 1) / totalSpins * Duration;
AddNested(i < SpinsRequired
- ? new SpinnerTick { StartTime = startTime }
- : new SpinnerBonusTick { StartTime = startTime });
+ ? new SpinnerTick { StartTime = startTime, Position = Position }
+ : new SpinnerBonusTick { StartTime = startTime, Position = Position });
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs
index f8a6e1d3c9..a1184a15cd 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBackgroundLayer.cs
@@ -3,15 +3,13 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Graphics;
-using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Skinning.Default
{
public class SpinnerBackgroundLayer : SpinnerFill
{
[BackgroundDependencyLoader]
- private void load(OsuColour colours, DrawableHitObject drawableHitObject)
+ private void load()
{
Disc.Alpha = 0;
Anchor = Anchor.Centre;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
index 611ddd08eb..b511444c44 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private GameplayState gameplayState { get; set; }
[BackgroundDependencyLoader]
- private void load(ISkinSource skin, OsuColour colours)
+ private void load(ISkinSource skin)
{
var texture = skin.GetTexture("star2");
var starBreakAdditive = skin.GetConfig(OsuSkinColour.StarBreakAdditive)?.Value ?? new Color4(255, 182, 193, 255);
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
index 6953e66b5c..7b9cf8e1d1 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
CursorExpand,
CursorRotate,
HitCircleOverlayAboveNumber,
+
+ // ReSharper disable once IdentifierTypo
HitCircleOverlayAboveNumer, // Some old skins will have this typo
SpinnerFrequencyModulate,
SpinnerNoBlink
diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs
index db4a6eb50b..6c76da7925 100644
--- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs
+++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs
@@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
pointGrid.Content = points;
- if (score.HitEvents == null || score.HitEvents.Count == 0)
+ if (score.HitEvents.Count == 0)
return;
// Todo: This should probably not be done like this.
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
index d1d9ee9f4d..b60ea5da21 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private OsuConfigManager config { get; set; }
[BackgroundDependencyLoader(true)]
- private void load(OsuConfigManager config, OsuRulesetConfigManager rulesetConfig)
+ private void load(OsuRulesetConfigManager rulesetConfig)
{
rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail);
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
index 4bdb85ba60..f5e7304c12 100644
--- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
@@ -32,12 +32,12 @@ namespace osu.Game.Rulesets.Taiko.Tests
HitObjects = new List { new Hit { Type = HitType.Centre } },
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty(),
+ Difficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
Title = @"Sample Beatmap",
- AuthorString = @"peppy",
+ Author = { Username = @"peppy" },
},
Ruleset = new TaikoRuleset().RulesetInfo
},
diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs
deleted file mode 100644
index 42ab84714a..0000000000
--- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorSaving.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Linq;
-using NUnit.Framework;
-using osu.Framework.Input;
-using osu.Framework.Testing;
-using osu.Framework.Utils;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Taiko.Beatmaps;
-using osu.Game.Screens.Edit;
-using osu.Game.Screens.Edit.Setup;
-using osu.Game.Screens.Menu;
-using osu.Game.Screens.Select;
-using osu.Game.Tests.Visual;
-using osuTK.Input;
-
-namespace osu.Game.Rulesets.Taiko.Tests.Editor
-{
- public class TestSceneEditorSaving : OsuGameTestScene
- {
- private Screens.Edit.Editor editor => Game.ChildrenOfType().FirstOrDefault();
-
- private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap));
-
- ///
- /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select.
- /// Emphasis is placed on , since taiko has special handling for it to keep compatibility with stable.
- ///
- [Test]
- public void TestNewBeatmapSaveThenLoad()
- {
- AddStep("set default beatmap", () => Game.Beatmap.SetDefault());
- AddStep("set taiko ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
-
- PushAndConfirm(() => new EditorLoader());
-
- AddUntilStep("wait for editor load", () => editor?.IsLoaded == true);
-
- AddUntilStep("wait for metadata screen load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
-
- // We intentionally switch away from the metadata screen, else there is a feedback loop with the textbox handling which causes metadata changes below to get overwritten.
-
- AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
- AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
-
- AddStep("Set slider multiplier", () => editorBeatmap.Difficulty.SliderMultiplier = 2);
- AddStep("Set artist and title", () =>
- {
- editorBeatmap.BeatmapInfo.Metadata.Artist = "artist";
- editorBeatmap.BeatmapInfo.Metadata.Title = "title";
- });
- AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty");
-
- checkMutations();
-
- AddStep("Save", () => InputManager.Keys(PlatformAction.Save));
-
- checkMutations();
-
- AddStep("Exit", () => InputManager.Key(Key.Escape));
-
- AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
-
- PushAndConfirm(() => new PlaySongSelect());
-
- AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
- AddStep("Open options", () => InputManager.Key(Key.F3));
- AddStep("Enter editor", () => InputManager.Key(Key.Number5));
-
- AddUntilStep("Wait for editor load", () => editor != null);
-
- checkMutations();
- }
-
- private void checkMutations()
- {
- AddAssert("Beatmap has correct slider multiplier", () =>
- {
- // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use.
- // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct.
- var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty();
- taikoDifficulty.CopyFrom(editorBeatmap.Difficulty);
- return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2);
- });
- AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
- AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty");
- }
- }
-}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs
new file mode 100644
index 0000000000..33c2ba532e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorSaving.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests.Editor
+{
+ public class TestSceneTaikoEditorSaving : EditorSavingTestScene
+ {
+ protected override Ruleset CreateRuleset() => new TaikoRuleset();
+
+ [Test]
+ public void TestTaikoSliderMultiplier()
+ {
+ AddStep("Set slider multiplier", () => EditorBeatmap.Difficulty.SliderMultiplier = 2);
+
+ SaveEditor();
+
+ AddAssert("Beatmap has correct slider multiplier", assertTaikoSliderMulitplier);
+
+ ReloadEditorToSameBeatmap();
+
+ AddAssert("Beatmap still has correct slider multiplier", assertTaikoSliderMulitplier);
+
+ bool assertTaikoSliderMulitplier()
+ {
+ // we can only assert value correctness on TaikoMultiplierAppliedDifficulty, because that is the final difficulty converted taiko beatmaps use.
+ // therefore, ensure that we have that difficulty type by calling .CopyFrom(), which is a no-op if the type is already correct.
+ var taikoDifficulty = new TaikoBeatmapConverter.TaikoMultiplierAppliedDifficulty();
+ taikoDifficulty.CopyFrom(EditorBeatmap.Difficulty);
+ return Precision.AlmostEquals(taikoDifficulty.SliderMultiplier, 2);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs
index 626537053a..55eb2fa66b 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs
@@ -40,10 +40,10 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor
{
InternalChildren = new Drawable[]
{
- EditorBeatmap = new EditorBeatmap(new TaikoBeatmap())
+ EditorBeatmap = new EditorBeatmap(new TaikoBeatmap
{
BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }
- },
+ }),
new TaikoHitObjectComposer(new TaikoRuleset())
};
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
index b976735223..920a7cd1a1 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
@@ -158,12 +158,12 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
HitObjects = new List { new Hit { Type = HitType.Centre } },
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty(),
+ Difficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = "Unknown",
Title = "Sample Beatmap",
- AuthorString = "Craftplacer",
+ Author = { Username = "Craftplacer" },
},
Ruleset = new TaikoRuleset().RulesetInfo
},
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index b6db333dc9..b3f6a733d3 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -12,7 +12,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
- [Timeout(10000)]
public class TaikoBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs
new file mode 100644
index 0000000000..060c3c9443
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollJudgements.cs
@@ -0,0 +1,36 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneDrumRollJudgements : TestSceneTaikoPlayer
+ {
+ [Test]
+ public void TestStrongDrumRollFullyJudgedOnKilled()
+ {
+ AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted.Value);
+ AddAssert("all judgements are misses", () => Player.Results.All(r => r.Type == r.Judgement.MinResult));
+ }
+
+ protected override bool Autoplay => false;
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
+ {
+ BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo },
+ HitObjects =
+ {
+ new DrumRoll
+ {
+ StartTime = 1000,
+ Duration = 1000,
+ IsStrong = true
+ }
+ }
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 9b2e9fedc5..b1d8575de4 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Beatmap converted = base.ConvertBeatmap(original, cancellationToken);
- if (original.BeatmapInfo.RulesetID == 3)
+ if (original.BeatmapInfo.Ruleset.OnlineID == 3)
{
// Post processing step to transform mania hit objects with the same start time into strong hits
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
@@ -191,6 +191,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
protected override Beatmap CreateBeatmap() => new TaikoBeatmap();
+ // Important to note that this is subclassing a realm object.
+ // Realm doesn't allow this, but for now this can work since we aren't (in theory?) persisting this to the database.
+ // It is only used during beatmap conversion and processing.
internal class TaikoMultiplierAppliedDifficulty : BeatmapDifficulty
{
public TaikoMultiplierAppliedDifficulty(IBeatmapDifficultyInfo difficulty)
@@ -205,6 +208,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
#region Overrides of BeatmapDifficulty
+ public override BeatmapDifficulty Clone() => new TaikoMultiplierAppliedDifficulty(this);
+
public override void CopyTo(BeatmapDifficulty other)
{
base.CopyTo(other);
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
index 0a325f174e..fb07c687bb 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
@@ -4,6 +4,7 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Layout;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
@@ -16,9 +17,26 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public override double ScoreMultiplier => 1.12;
- private const float default_flashlight_size = 250;
+ [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
+ public override BindableNumber SizeMultiplier { get; } = new BindableNumber
+ {
+ MinValue = 0.5f,
+ MaxValue = 1.5f,
+ Default = 1f,
+ Value = 1f,
+ Precision = 0.1f
+ };
- public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield);
+ [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
+ public override BindableBool ComboBasedSize { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
+ public override float DefaultFlashlightSize => 250;
+
+ protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield);
private TaikoPlayfield playfield;
@@ -33,7 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize);
private readonly TaikoPlayfield taikoPlayfield;
- public TaikoFlashlight(TaikoPlayfield taikoPlayfield)
+ public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield)
+ : base(modFlashlight)
{
this.taikoPlayfield = taikoPlayfield;
FlashlightSize = getSizeFor(0);
@@ -43,15 +62,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
private Vector2 getSizeFor(int combo)
{
- float size = default_flashlight_size;
-
- if (combo > 200)
- size *= 0.8f;
- else if (combo > 100)
- size *= 0.9f;
-
// Preserve flashlight size through the playfield's aspect adjustment.
- return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
+ return new Vector2(0, GetSizeFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
}
protected override void OnComboChange(ValueChangedEvent e)
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index 521189d36c..b84db513f7 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -197,6 +197,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
+ public override void OnKilled()
+ {
+ base.OnKilled();
+
+ if (Time.Current > ParentHitObject.HitObject.GetEndTime() && !Judged)
+ ApplyResult(r => r.Type = r.Judgement.MinResult);
+ }
+
public override bool OnPressed(KeyBindingPressEvent e) => false;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index dc2ed200a1..e24923e482 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -5,6 +5,7 @@ using System;
using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Skinning.Default;
using osu.Game.Skinning;
@@ -52,6 +53,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = r.Judgement.MaxResult);
}
+ public override void OnKilled()
+ {
+ base.OnKilled();
+
+ if (Time.Current > HitObject.GetEndTime() && !Judged)
+ ApplyResult(r => r.Type = r.Judgement.MinResult);
+ }
+
protected override void UpdateHitStateTransforms(ArmedState state)
{
switch (state)
@@ -92,6 +101,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
+ public override void OnKilled()
+ {
+ base.OnKilled();
+
+ if (Time.Current > ParentHitObject.HitObject.GetEndTime() && !Judged)
+ ApplyResult(r => r.Type = r.Judgement.MinResult);
+ }
+
public override bool OnPressed(KeyBindingPressEvent e) => false;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs
index 455b2fc596..25f895708f 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Default/CentreHitCirclePiece.cs
@@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects;
using osuTK;
@@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
AccentColour = Hit.COLOUR_CENTRE;
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs
index 8ca996159b..a106c4f629 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs
@@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
if (!effectPoint.KiaiMode)
return;
- if (beatIndex % (int)timingPoint.TimeSignature != 0)
+ if (beatIndex % timingPoint.TimeSignature.Numerator != 0)
return;
double duration = timingPoint.BeatLength * 2;
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs
index bd21d511b1..c6165495d8 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Default/RimHitCirclePiece.cs
@@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects;
using osuTK;
using osuTK.Graphics;
@@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
AccentColour = Hit.COLOUR_RIM;
}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs
index e1063e1071..7ba2618a63 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs
@@ -7,7 +7,6 @@ using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
@@ -39,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.UI
}
[BackgroundDependencyLoader(true)]
- private void load(TextureStore textures, GameplayState gameplayState)
+ private void load(GameplayState gameplayState)
{
InternalChildren = new[]
{
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 677aaf6f78..468cb7683c 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
Assert.AreEqual(164471, metadata.PreviewTime);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
- Assert.IsTrue(beatmapInfo.RulesetID == 0);
+ Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
Assert.IsFalse(beatmapInfo.SpecialStyle);
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
@@ -117,7 +117,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(string.Empty, metadata.Source);
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
Assert.AreEqual(557821, beatmapInfo.OnlineID);
- Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineID);
+ Assert.AreEqual(241526, beatmapInfo.BeatmapSet?.OnlineID);
}
}
@@ -178,17 +178,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
var timingPoint = controlPoints.TimingPointAt(0);
Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
- Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
+ Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
timingPoint = controlPoints.TimingPointAt(48428);
Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
- Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
+ Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
timingPoint = controlPoints.TimingPointAt(119637);
Assert.AreEqual(119637, timingPoint.Time);
Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
- Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
+ Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
var difficultyPoint = controlPoints.DifficultyPointAt(0);
Assert.AreEqual(0, difficultyPoint.Time);
@@ -794,5 +794,74 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(path.Distance, Is.EqualTo(1));
}
}
+
+ [Test]
+ public void TestLegacyDefaultsPreserved()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var memoryStream = new MemoryStream())
+ using (var stream = new LineBufferedReader(memoryStream))
+ {
+ var decoded = decoder.Decode(stream);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0));
+ Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f));
+ Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False);
+ Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False);
+ Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False);
+ Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False);
+ Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False);
+ Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal));
+ Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0));
+ Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1));
+ Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0));
+ });
+ }
+ }
+
+ [Test]
+ public void TestUndefinedApproachRateInheritsOverallDifficulty()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var resStream = TestResources.OpenResource("undefined-approach-rate.osu"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var decoded = decoder.Decode(stream);
+ Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(1));
+ Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
+ }
+ }
+
+ [Test]
+ public void TestApproachRateDefinedBeforeOverallDifficulty()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var resStream = TestResources.OpenResource("approach-rate-before-overall-difficulty.osu"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var decoded = decoder.Decode(stream);
+ Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9));
+ Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
+ }
+ }
+
+ [Test]
+ public void TestApproachRateDefinedAfterOverallDifficulty()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var resStream = TestResources.OpenResource("approach-rate-after-overall-difficulty.osu"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var decoded = decoder.Decode(stream);
+ Assert.That(decoded.Difficulty.ApproachRate, Is.EqualTo(9));
+ Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1));
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
index d12da1a22f..d19b3c71f1 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -195,7 +195,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
private IBeatmap convert(IBeatmap beatmap)
{
- switch (beatmap.BeatmapInfo.RulesetID)
+ switch (beatmap.BeatmapInfo.Ruleset.OnlineID)
{
case 0:
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
index 81d89359e0..2ba8c51a10 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
@@ -12,6 +12,7 @@ using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.UI;
@@ -51,6 +52,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(829_931, score.ScoreInfo.TotalScore);
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
+
+ Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic));
+ Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL"));
+ Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL"));
+
Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001));
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
@@ -95,7 +101,6 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(decodedAfterEncode, Is.Not.Null);
Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username));
- Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfoID, Is.EqualTo(scoreInfo.BeatmapInfoID));
Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset));
Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore));
Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo));
@@ -129,7 +134,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
MD5Hash = md5Hash,
Ruleset = new OsuRuleset().RulesetInfo,
- BaseDifficulty = new BeatmapDifficulty()
+ Difficulty = new BeatmapDifficulty()
}
});
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index bfd6ff0314..2eb75259d9 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var beatmap = decodeAsJson(normal);
var meta = beatmap.BeatmapInfo.Metadata;
- Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID);
+ Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet?.OnlineID);
Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
- Assert.IsTrue(beatmapInfo.RulesetID == 0);
+ Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
new file mode 100644
index 0000000000..9e440c6bce
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
@@ -0,0 +1,85 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Database;
+using osu.Game.Tests.Database;
+using osu.Game.Tests.Resources;
+
+namespace osu.Game.Tests.Beatmaps.IO
+{
+ public static class BeatmapImportHelper
+ {
+ public static async Task LoadQuickOszIntoOsu(OsuGameBase osu)
+ {
+ string temp = TestResources.GetQuickTestBeatmapForImport();
+
+ var manager = osu.Dependencies.Get();
+
+ var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
+
+ Debug.Assert(importedSet != null);
+
+ ensureLoaded(osu);
+
+ waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
+
+ return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
+ }
+
+ public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
+ {
+ string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
+
+ var manager = osu.Dependencies.Get();
+
+ var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
+
+ Debug.Assert(importedSet != null);
+
+ ensureLoaded(osu);
+
+ waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
+
+ return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
+ }
+
+ private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
+ {
+ var realm = osu.Dependencies.Get();
+
+ realm.Run(r => BeatmapImporterTests.EnsureLoaded(r, timeout));
+
+ // TODO: add back some extra checks outside of the realm ones?
+ // var set = queryBeatmapSets().First();
+ // foreach (BeatmapInfo b in set.Beatmaps)
+ // Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
+ // Assert.IsTrue(set.Beatmaps.Count > 0);
+ // var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
+ // Assert.IsTrue(beatmap?.HitObjects.Any() == true);
+ // beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
+ // Assert.IsTrue(beatmap?.HitObjects.Any() == true);
+ // beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
+ // Assert.IsTrue(beatmap?.HitObjects.Any() == true);
+ // beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
+ // Assert.IsTrue(beatmap?.HitObjects.Any() == true);
+ }
+
+ private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000)
+ {
+ Task task = Task.Run(() =>
+ {
+ while (!result()) Thread.Sleep(200);
+ });
+
+ Assert.IsTrue(task.Wait(timeout), failureMessage);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
deleted file mode 100644
index 9f3709f7a3..0000000000
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ /dev/null
@@ -1,1104 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.IO;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using NUnit.Framework;
-using osu.Framework.Platform;
-using osu.Game.IPC;
-using osu.Framework.Allocation;
-using osu.Framework.Extensions;
-using osu.Framework.Extensions.ObjectExtensions;
-using osu.Framework.Logging;
-using osu.Game.Beatmaps;
-using osu.Game.Database;
-using osu.Game.Extensions;
-using osu.Game.IO;
-using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Overlays.Notifications;
-using osu.Game.Rulesets.Osu;
-using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Scoring;
-using osu.Game.Tests.Resources;
-using osu.Game.Tests.Scores.IO;
-using SharpCompress.Archives;
-using SharpCompress.Archives.Zip;
-using SharpCompress.Common;
-using SharpCompress.Writers.Zip;
-using FileInfo = System.IO.FileInfo;
-
-namespace osu.Game.Tests.Beatmaps.IO
-{
- [TestFixture]
- public class ImportBeatmapTest : ImportTest
- {
- [Test]
- public async Task TestImportWhenClosed()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- await LoadOszIntoOsu(LoadOsuIntoHost(host));
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenDelete()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var imported = await LoadOszIntoOsu(osu);
-
- deleteBeatmapSet(imported, osu);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenDeleteFromStream()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string tempPath = TestResources.GetTestBeatmapForImport();
-
- var manager = osu.Dependencies.Get();
-
- ILive importedSet;
-
- using (var stream = File.OpenRead(tempPath))
- {
- importedSet = await manager.Import(new ImportTask(stream, Path.GetFileName(tempPath)));
- ensureLoaded(osu);
- }
-
- Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
- File.Delete(tempPath);
-
- var imported = manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
-
- deleteBeatmapSet(imported, osu);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenImport()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var imported = await LoadOszIntoOsu(osu);
- var importedSecondTime = await LoadOszIntoOsu(osu);
-
- // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
- Assert.IsTrue(imported.ID == importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
-
- checkBeatmapSetCount(osu, 1);
- checkSingleReferencedFileCount(osu, 18);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenImportWithReZip()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- Directory.CreateDirectory(extractedFolder);
-
- try
- {
- var imported = await LoadOszIntoOsu(osu);
-
- string hashBefore = hashFile(temp);
-
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(extractedFolder);
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- // zip files differ because different compression or encoder.
- Assert.AreNotEqual(hashBefore, hashFile(temp));
-
- var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- ensureLoaded(osu);
-
- // but contents doesn't, so existing should still be used.
- Assert.IsTrue(imported.ID == importedSecondTime.Value.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Value.Beatmaps.First().ID);
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenImportWithChangedHashedFile()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- Directory.CreateDirectory(extractedFolder);
-
- try
- {
- var imported = await LoadOszIntoOsu(osu);
-
- await createScoreForBeatmap(osu, imported.Beatmaps.First());
-
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(extractedFolder);
-
- // arbitrary write to hashed file
- // this triggers the special BeatmapManager.PreImport deletion/replacement flow.
- using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).AppendText())
- await sw.WriteLineAsync("// changed");
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- ensureLoaded(osu);
-
- // check the newly "imported" beatmap is not the original.
- Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- [Ignore("intentionally broken by import optimisations")]
- public async Task TestImportThenImportWithChangedFile()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- Directory.CreateDirectory(extractedFolder);
-
- try
- {
- var imported = await LoadOszIntoOsu(osu);
-
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(extractedFolder);
-
- // arbitrary write to non-hashed file
- using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.mp3").First()).AppendText())
- await sw.WriteLineAsync("text");
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- ensureLoaded(osu);
-
- // check the newly "imported" beatmap is not the original.
- Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenImportWithDifferentFilename()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- Directory.CreateDirectory(extractedFolder);
-
- try
- {
- var imported = await LoadOszIntoOsu(osu);
-
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(extractedFolder);
-
- // change filename
- var firstFile = new FileInfo(Directory.GetFiles(extractedFolder).First());
- firstFile.MoveTo(Path.Combine(firstFile.DirectoryName.AsNonNull(), $"{firstFile.Name}-changed{firstFile.Extension}"));
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- var importedSecondTime = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- ensureLoaded(osu);
-
- // check the newly "imported" beatmap is not the original.
- Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- [Ignore("intentionally broken by import optimisations")]
- public async Task TestImportCorruptThenImport()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var imported = await LoadOszIntoOsu(osu);
-
- var firstFile = imported.Files.First();
-
- var files = osu.Dependencies.Get();
-
- long originalLength;
- using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath()))
- originalLength = stream.Length;
-
- using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath(), FileAccess.Write, FileMode.Create))
- stream.WriteByte(0);
-
- var importedSecondTime = await LoadOszIntoOsu(osu);
-
- using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath()))
- Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
-
- // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
- Assert.IsTrue(imported.ID == importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
-
- checkBeatmapSetCount(osu, 1);
- checkSingleReferencedFileCount(osu, 18);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestModelCreationFailureDoesntReturn()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- var importer = osu.Dependencies.Get();
-
- var progressNotification = new ImportProgressNotification();
-
- var zipStream = new MemoryStream();
-
- using (var zip = ZipArchive.Create())
- zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate));
-
- var imported = await importer.Import(
- progressNotification,
- new ImportTask(zipStream, string.Empty)
- );
-
- checkBeatmapSetCount(osu, 0);
- checkBeatmapCount(osu, 0);
-
- Assert.IsEmpty(imported);
- Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestRollbackOnFailure()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- int itemAddRemoveFireCount = 0;
- int loggedExceptionCount = 0;
-
- Logger.NewEntry += l =>
- {
- if (l.Target == LoggingTarget.Database && l.Exception != null)
- Interlocked.Increment(ref loggedExceptionCount);
- };
-
- var osu = LoadOsuIntoHost(host);
- var manager = osu.Dependencies.Get();
-
- // ReSharper disable once AccessToModifiedClosure
- manager.ItemUpdated += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
- manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
-
- var imported = await LoadOszIntoOsu(osu);
-
- Assert.AreEqual(0, itemAddRemoveFireCount -= 1);
-
- imported.Hash += "-changed";
- manager.Update(imported);
-
- Assert.AreEqual(0, itemAddRemoveFireCount -= 1);
-
- checkBeatmapSetCount(osu, 1);
- checkBeatmapCount(osu, 12);
- checkSingleReferencedFileCount(osu, 18);
-
- string brokenTempFilename = TestResources.GetTestBeatmapForImport();
-
- MemoryStream brokenOsu = new MemoryStream();
- MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(brokenTempFilename));
-
- File.Delete(brokenTempFilename);
-
- using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew))
- using (var zip = ZipArchive.Open(brokenOsz))
- {
- zip.AddEntry("broken.osu", brokenOsu, false);
- zip.SaveTo(outStream, CompressionType.Deflate);
- }
-
- // this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
- try
- {
- await manager.Import(new ImportTask(brokenTempFilename));
- }
- catch
- {
- }
-
- // no events should be fired in the case of a rollback.
- Assert.AreEqual(0, itemAddRemoveFireCount);
-
- checkBeatmapSetCount(osu, 1);
- checkBeatmapCount(osu, 12);
-
- checkSingleReferencedFileCount(osu, 18);
-
- Assert.AreEqual(1, loggedExceptionCount);
-
- File.Delete(brokenTempFilename);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenDeleteThenImport()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var imported = await LoadOszIntoOsu(osu);
-
- deleteBeatmapSet(imported, osu);
-
- var importedSecondTime = await LoadOszIntoOsu(osu);
-
- // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
- Assert.IsTrue(imported.ID == importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportThenDeleteThenImportWithOnlineIDsMissing()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var imported = await LoadOszIntoOsu(osu);
-
- foreach (var b in imported.Beatmaps)
- b.OnlineID = null;
-
- osu.Dependencies.Get().Update(imported);
-
- deleteBeatmapSet(imported, osu);
-
- var importedSecondTime = await LoadOszIntoOsu(osu);
-
- // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
- Assert.IsTrue(imported.ID != importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportWithDuplicateBeatmapIDs()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var metadata = new BeatmapMetadata
- {
- Artist = "SomeArtist",
- AuthorString = "SomeAuthor"
- };
-
- var difficulty = new BeatmapDifficulty();
-
- var toImport = new BeatmapSetInfo
- {
- OnlineID = 1,
- Metadata = metadata,
- Beatmaps =
- {
- new BeatmapInfo
- {
- OnlineID = 2,
- Metadata = metadata,
- BaseDifficulty = difficulty
- },
- new BeatmapInfo
- {
- OnlineID = 2,
- Metadata = metadata,
- Status = BeatmapOnlineStatus.Loved,
- BaseDifficulty = difficulty
- }
- }
- };
-
- var manager = osu.Dependencies.Get();
-
- var imported = await manager.Import(toImport);
-
- Assert.NotNull(imported);
- Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineID);
- Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineID);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- [NonParallelizable]
- public void TestImportOverIPC()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(true))
- using (HeadlessGameHost client = new CleanRunHeadlessGameHost(true))
- {
- try
- {
- Assert.IsTrue(host.IsPrimaryInstance);
- Assert.IsFalse(client.IsPrimaryInstance);
-
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- var importer = new ArchiveImportIPCChannel(client);
- if (!importer.ImportAsync(temp).Wait(10000))
- Assert.Fail(@"IPC took too long to send");
-
- ensureLoaded(osu);
-
- waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportWhenFileOpen()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- string temp = TestResources.GetTestBeatmapForImport();
- using (File.OpenRead(temp))
- await osu.Dependencies.Get().Import(temp);
- ensureLoaded(osu);
- File.Delete(temp);
- Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportWithDuplicateHashes()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- Directory.CreateDirectory(extractedFolder);
-
- try
- {
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(extractedFolder);
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.AddEntry("duplicate.osu", Directory.GetFiles(extractedFolder, "*.osu").First());
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- await osu.Dependencies.Get().Import(temp);
-
- ensureLoaded(osu);
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportNestedStructure()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- string subfolder = Path.Combine(extractedFolder, "subfolder");
-
- Directory.CreateDirectory(subfolder);
-
- try
- {
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(subfolder);
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- var imported = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- ensureLoaded(osu);
-
- Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder");
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestImportWithIgnoredDirectoryInArchive()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- string temp = TestResources.GetTestBeatmapForImport();
-
- string extractedFolder = $"{temp}_extracted";
- string dataFolder = Path.Combine(extractedFolder, "actual_data");
- string resourceForkFolder = Path.Combine(extractedFolder, "__MACOSX");
- string resourceForkFilePath = Path.Combine(resourceForkFolder, ".extracted");
-
- Directory.CreateDirectory(dataFolder);
- Directory.CreateDirectory(resourceForkFolder);
-
- using (var resourceForkFile = File.CreateText(resourceForkFilePath))
- {
- await resourceForkFile.WriteLineAsync("adding content so that it's not empty");
- }
-
- try
- {
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(dataFolder);
-
- using (var zip = ZipArchive.Create())
- {
- zip.AddAllFromDirectory(extractedFolder);
- zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
- }
-
- var imported = await osu.Dependencies.Get().Import(new ImportTask(temp));
-
- ensureLoaded(osu);
-
- Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored");
- Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder");
- }
- finally
- {
- Directory.Delete(extractedFolder, true);
- }
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestUpdateBeatmapInfo()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- var manager = osu.Dependencies.Get();
-
- string temp = TestResources.GetTestBeatmapForImport();
- await osu.Dependencies.Get().Import(temp);
-
- // Update via the beatmap, not the beatmap info, to ensure correct linking
- BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
- Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap;
- beatmapToUpdate.BeatmapInfo.DifficultyName = "updated";
-
- manager.Update(setToUpdate);
-
- BeatmapInfo updatedInfo = manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID);
- Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated"));
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestUpdateBeatmapFile()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- var manager = osu.Dependencies.Get();
-
- string temp = TestResources.GetTestBeatmapForImport();
- await osu.Dependencies.Get().Import(temp);
-
- BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
-
- var beatmapInfo = setToUpdate.Beatmaps.First(b => b.RulesetID == 0);
- Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap;
- BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename));
-
- string oldMd5Hash = beatmapToUpdate.BeatmapInfo.MD5Hash;
-
- beatmapToUpdate.HitObjects.Clear();
- beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 });
-
- manager.Save(beatmapInfo, beatmapToUpdate);
-
- // Check that the old file reference has been removed
- Assert.That(manager.QueryBeatmapSet(s => s.ID == setToUpdate.ID).Files.All(f => f.ID != fileToUpdate.ID));
-
- // Check that the new file is referenced correctly by attempting a retrieval
- Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID)).Beatmap;
- Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1));
- Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000));
- Assert.That(updatedBeatmap.BeatmapInfo.MD5Hash, Is.Not.EqualTo(oldMd5Hash));
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- // TODO: needs to be pulled across to realm implementation when this file is nuked.
- [Test]
- public void TestSaveRemovesInvalidCharactersFromPath()
- {
- // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
-
- var manager = osu.Dependencies.Get();
-
- var working = manager.CreateNew(new OsuRuleset().RulesetInfo, APIUser.SYSTEM_USER);
-
- var beatmap = working.Beatmap;
-
- beatmap.BeatmapInfo.DifficultyName = "difficulty";
- beatmap.BeatmapInfo.Metadata = new BeatmapMetadata
- {
- Artist = "Artist/With\\Slashes",
- Title = "Title",
- AuthorString = "mapper",
- };
-
- manager.Save(beatmap.BeatmapInfo, working.Beatmap);
-
- Assert.AreEqual("Artist_With_Slashes - Title (mapper) [difficulty].osu", beatmap.BeatmapInfo.Path);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public void TestCreateNewEmptyBeatmap()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- var manager = osu.Dependencies.Get();
-
- var working = manager.CreateNew(new OsuRuleset().RulesetInfo, APIUser.SYSTEM_USER);
-
- manager.Save(working.BeatmapInfo, working.Beatmap);
-
- var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
-
- // Check that the new file is referenced correctly by attempting a retrieval
- Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
- Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(0));
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public void TestCreateNewBeatmapWithObject()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host);
- var manager = osu.Dependencies.Get();
-
- var working = manager.CreateNew(new OsuRuleset().RulesetInfo, APIUser.SYSTEM_USER);
-
- ((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 });
-
- manager.Save(working.BeatmapInfo, working.Beatmap);
-
- var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
-
- // Check that the new file is referenced correctly by attempting a retrieval
- Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
- Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1));
- Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000));
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- public static async Task LoadQuickOszIntoOsu(OsuGameBase osu)
- {
- string temp = TestResources.GetQuickTestBeatmapForImport();
-
- var manager = osu.Dependencies.Get();
-
- var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
-
- ensureLoaded(osu);
-
- waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
-
- return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
- }
-
- public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
- {
- string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
-
- var manager = osu.Dependencies.Get();
-
- var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
-
- ensureLoaded(osu);
-
- waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
-
- return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
- }
-
- private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)
- {
- var manager = osu.Dependencies.Get();
- manager.Delete(imported);
-
- checkBeatmapSetCount(osu, 0);
- checkBeatmapSetCount(osu, 1, true);
- checkSingleReferencedFileCount(osu, 0);
-
- Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
- }
-
- private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmapInfo)
- {
- return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
- {
- OnlineID = 2,
- BeatmapInfo = beatmapInfo,
- BeatmapInfoID = beatmapInfo.ID
- }, new ImportScoreTest.TestArchiveReader());
- }
-
- private static void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false)
- {
- var manager = osu.Dependencies.Get();
-
- Assert.AreEqual(expected, includeDeletePending
- ? manager.QueryBeatmapSets(_ => true).ToList().Count
- : manager.GetAllUsableBeatmapSets().Count);
- }
-
- private static string hashFile(string filename)
- {
- using (var s = File.OpenRead(filename))
- return s.ComputeMD5Hash();
- }
-
- private static void checkBeatmapCount(OsuGameBase osu, int expected)
- {
- Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count);
- }
-
- private static void checkSingleReferencedFileCount(OsuGameBase osu, int expected)
- {
- Assert.AreEqual(expected, osu.Dependencies.Get().Get().FileInfo.Count(f => f.ReferenceCount == 1));
- }
-
- private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
- {
- IEnumerable resultSets = null;
- var store = osu.Dependencies.Get();
- waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineID == 241526)).Any(),
- @"BeatmapSet did not import to the database in allocated time.", timeout);
-
- // ensure we were stored to beatmap database backing...
- Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1).");
- IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineID == 241526 && s.BaseDifficultyID > 0);
- IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineID == 241526);
-
- // if we don't re-check here, the set will be inserted but the beatmaps won't be present yet.
- waitForOrAssert(() => queryBeatmaps().Count() == 12,
- @"Beatmaps did not import to the database in allocated time", timeout);
- waitForOrAssert(() => queryBeatmapSets().Count() == 1,
- @"BeatmapSet did not import to the database in allocated time", timeout);
- int countBeatmapSetBeatmaps = 0;
- int countBeatmaps = 0;
- waitForOrAssert(() =>
- (countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) ==
- (countBeatmaps = queryBeatmaps().Count()),
- $@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout);
-
- var set = queryBeatmapSets().First();
- foreach (BeatmapInfo b in set.Beatmaps)
- Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
- Assert.IsTrue(set.Beatmaps.Count > 0);
- var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Any() == true);
- beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Any() == true);
- beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Any() == true);
- beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
- Assert.IsTrue(beatmap?.HitObjects.Any() == true);
- }
-
- private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000)
- {
- Task task = Task.Run(() =>
- {
- while (!result()) Thread.Sleep(200);
- });
-
- Assert.IsTrue(task.Wait(timeout), failureMessage);
- }
- }
-}
diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
index b2ab1eeaa6..810ea5dbd0 100644
--- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var meta = beatmap.Metadata;
- Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID);
+ Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet?.OnlineID);
Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
index 3a82cbc785..f3456cf8e4 100644
--- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
+++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
@@ -23,6 +24,8 @@ namespace osu.Game.Tests.Beatmaps
{
public const double BASE_STARS = 5.55;
+ private static readonly Guid guid = Guid.NewGuid();
+
private BeatmapSetInfo importedSet;
private TestBeatmapDifficultyCache difficultyCache;
@@ -32,7 +35,7 @@ namespace osu.Game.Tests.Beatmaps
[BackgroundDependencyLoader]
private void load(OsuGameBase osu)
{
- importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).Result;
+ importedSet = BeatmapImportHelper.LoadQuickOszIntoOsu(osu).GetResultSafely();
}
[SetUpSteps]
@@ -97,8 +100,8 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestKeyEqualsWithDifferentModInstances()
{
- var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
- var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
@@ -107,8 +110,8 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestKeyEqualsWithDifferentModOrder()
{
- var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
- var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
@@ -117,8 +120,8 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestKeyDoesntEqualWithDifferentModSettings()
{
- var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
- var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
Assert.That(key1, Is.Not.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode()));
@@ -127,8 +130,8 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestKeyEqualWithMatchingModSettings()
{
- var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
- var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
+ var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
+ var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
diff --git a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs
index bf5b517603..153788c2cf 100644
--- a/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs
+++ b/osu.Game.Tests/Beatmaps/TestSceneEditorBeatmap.cs
@@ -7,6 +7,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
@@ -30,7 +31,13 @@ namespace osu.Game.Tests.Beatmaps
AddStep("add beatmap", () =>
{
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ });
editorBeatmap.HitObjectAdded += h => addedObject = h;
});
@@ -49,7 +56,14 @@ namespace osu.Game.Tests.Beatmaps
EditorBeatmap editorBeatmap = null;
AddStep("add beatmap", () =>
{
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ HitObjects = { hitCircle }
+ });
editorBeatmap.HitObjectRemoved += h => removedObject = h;
});
AddStep("remove hitobject", () => editorBeatmap.Remove(editorBeatmap.HitObjects.First()));
@@ -71,7 +85,14 @@ namespace osu.Game.Tests.Beatmaps
{
EditorBeatmap editorBeatmap;
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ HitObjects = { hitCircle }
+ });
editorBeatmap.HitObjectUpdated += h => changedObject = h;
});
@@ -91,7 +112,13 @@ namespace osu.Game.Tests.Beatmaps
AddStep("add beatmap", () =>
{
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ });
editorBeatmap.HitObjectUpdated += h => changedObject = h;
});
@@ -111,7 +138,14 @@ namespace osu.Game.Tests.Beatmaps
public void TestRemovedHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ HitObjects = { hitCircle }
+ });
HitObject changedObject = null;
editorBeatmap.HitObjectUpdated += h => changedObject = h;
@@ -131,6 +165,10 @@ namespace osu.Game.Tests.Beatmaps
{
var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
HitObjects =
{
new HitCircle(),
@@ -156,6 +194,10 @@ namespace osu.Game.Tests.Beatmaps
var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
HitObjects =
{
new HitCircle(),
@@ -185,7 +227,13 @@ namespace osu.Game.Tests.Beatmaps
{
updatedObjects.Clear();
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ });
for (int i = 0; i < 10; i++)
{
@@ -220,7 +268,13 @@ namespace osu.Game.Tests.Beatmaps
{
updatedObjects.Clear();
- Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ Child = editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ });
editorBeatmap.Add(new HitCircle());
});
diff --git a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs
index 4a7d7505ad..10cac4ed9d 100644
--- a/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs
+++ b/osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs
@@ -3,7 +3,7 @@
using NUnit.Framework;
using osu.Game.Beatmaps;
-using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Models;
namespace osu.Game.Tests.Beatmaps
{
@@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps
{
Artist = "artist",
Title = "title",
- Author = new APIUser { Username = "creator" }
+ Author = new RealmUser { Username = "creator" }
}
};
@@ -50,7 +50,7 @@ namespace osu.Game.Tests.Beatmaps
{
Artist = "artist",
Title = "title",
- Author = new APIUser { Username = "creator" }
+ Author = new RealmUser { Username = "creator" }
},
DifficultyName = "difficulty"
};
diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs
index af87fc17ad..8def8005f1 100644
--- a/osu.Game.Tests/Chat/MessageFormatterTests.cs
+++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs
@@ -9,6 +9,21 @@ namespace osu.Game.Tests.Chat
[TestFixture]
public class MessageFormatterTests
{
+ private string originalWebsiteRootUrl;
+
+ [OneTimeSetUp]
+ public void OneTimeSetUp()
+ {
+ originalWebsiteRootUrl = MessageFormatter.WebsiteRootUrl;
+ MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
+ }
+
+ [OneTimeTearDown]
+ public void OneTimeTearDown()
+ {
+ MessageFormatter.WebsiteRootUrl = originalWebsiteRootUrl;
+ }
+
[Test]
public void TestBareLink()
{
@@ -32,8 +47,6 @@ namespace osu.Game.Tests.Chat
[TestCase(LinkAction.External, "https://dev.ppy.sh/beatmapsets/discussions/123", "https://dev.ppy.sh/beatmapsets/discussions/123")]
public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link)
{
- MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
-
Message result = MessageFormatter.FormatMessage(new Message { Content = link });
Assert.AreEqual(result.Content, result.DisplayContent);
@@ -47,7 +60,10 @@ namespace osu.Game.Tests.Chat
[Test]
public void TestMultipleComplexLinks()
{
- Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/" });
+ Message result = MessageFormatter.FormatMessage(new Message
+ {
+ Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/"
+ });
Assert.AreEqual(result.Content, result.DisplayContent);
Assert.AreEqual(3, result.Links.Count);
@@ -104,7 +120,7 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual("This is a Wiki Link.", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count);
- Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
+ Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(9, result.Links[0].Length);
}
@@ -117,15 +133,15 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual("This is a Wiki Link Wiki:LinkWiki.Link.", result.DisplayContent);
Assert.AreEqual(3, result.Links.Count);
- Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
+ Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(9, result.Links[0].Length);
- Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki:Link", result.Links[1].Url);
+ Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki:Link", result.Links[1].Url);
Assert.AreEqual(20, result.Links[1].Index);
Assert.AreEqual(9, result.Links[1].Length);
- Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki.Link", result.Links[2].Url);
+ Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki.Link", result.Links[2].Url);
Assert.AreEqual(29, result.Links[2].Index);
Assert.AreEqual(9, result.Links[2].Length);
}
@@ -445,12 +461,15 @@ namespace osu.Game.Tests.Chat
[Test]
public void TestLinkComplex()
{
- Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12" });
+ Message result = MessageFormatter.FormatMessage(new Message
+ {
+ Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12"
+ });
Assert.AreEqual("This is a simple test with some [traps] and wiki links. Don't forget to visit https://osu.ppy.sh now!\0\0\0", result.DisplayContent);
Assert.AreEqual(5, result.Links.Count);
- Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links");
+ Link f = result.Links.Find(l => l.Url == "https://dev.ppy.sh/wiki/wiki links");
Assert.That(f, Is.Not.Null);
Assert.AreEqual(44, f.Index);
Assert.AreEqual(10, f.Length);
@@ -514,8 +533,6 @@ namespace osu.Game.Tests.Chat
[TestCase("https://dev.ppy.sh/home/changelog/lazer/2021.1012", "lazer/2021.1012")]
public void TestChangelogLinks(string link, string expectedArg)
{
- MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
-
LinkDetails result = MessageFormatter.GetLinkDetails(link);
Assert.AreEqual(LinkAction.OpenChangelog, result.Action);
diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
index d4ec5e897b..5cbede54f5 100644
--- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
+++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
+using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Tests.Resources;
@@ -154,7 +155,7 @@ namespace osu.Game.Tests.Collections.IO
}
// Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location.
- using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName))
+ using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName, null))
{
try
{
@@ -179,7 +180,7 @@ namespace osu.Game.Tests.Collections.IO
{
// intentionally spin this up on a separate task to avoid disposal deadlocks.
// see https://github.com/EventStore/EventStore/issues/1179
- await Task.Run(() => osu.CollectionManager.Import(stream).Wait());
+ await Task.Factory.StartNew(() => osu.CollectionManager.Import(stream).WaitSafely(), TaskCreationOptions.LongRunning);
}
}
}
diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs
index e47e24021f..2c7d0211a0 100644
--- a/osu.Game.Tests/Database/BeatmapImporterTests.cs
+++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs
@@ -19,6 +19,7 @@ using osu.Game.Extensions;
using osu.Game.IO.Archives;
using osu.Game.Models;
using osu.Game.Overlays.Notifications;
+using osu.Game.Rulesets;
using osu.Game.Stores;
using osu.Game.Tests.Resources;
using Realms;
@@ -34,56 +35,157 @@ namespace osu.Game.Tests.Database
[TestFixture]
public class BeatmapImporterTests : RealmTest
{
+ [Test]
+ public void TestDetachBeatmapSet()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
+ {
+ Live? beatmapSet;
+
+ using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
+ beatmapSet = await importer.Import(reader);
+
+ Assert.NotNull(beatmapSet);
+ Debug.Assert(beatmapSet != null);
+
+ BeatmapSetInfo? detachedBeatmapSet = null;
+
+ beatmapSet.PerformRead(live =>
+ {
+ detachedBeatmapSet = live.Detach();
+
+ // files are omitted
+ Assert.AreEqual(0, detachedBeatmapSet.Files.Count);
+
+ Assert.AreEqual(live.Beatmaps.Count, detachedBeatmapSet.Beatmaps.Count);
+ Assert.AreEqual(live.Beatmaps.Select(f => f.Difficulty).Count(), detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
+ Assert.AreEqual(live.Metadata, detachedBeatmapSet.Metadata);
+ });
+
+ Debug.Assert(detachedBeatmapSet != null);
+
+ // Check detached instances can all be accessed without throwing.
+ Assert.AreEqual(0, detachedBeatmapSet.Files.Count);
+ Assert.NotNull(detachedBeatmapSet.Beatmaps.Count);
+ Assert.NotZero(detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
+ Assert.NotNull(detachedBeatmapSet.Metadata);
+
+ // Check cyclic reference to beatmap set
+ Assert.AreEqual(detachedBeatmapSet, detachedBeatmapSet.Beatmaps.First().BeatmapSet);
+ }
+ });
+ }
+
+ [Test]
+ public void TestUpdateDetachedBeatmapSet()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
+ {
+ Live? beatmapSet;
+
+ using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
+ beatmapSet = await importer.Import(reader);
+
+ Assert.NotNull(beatmapSet);
+ Debug.Assert(beatmapSet != null);
+
+ // Detach at the BeatmapInfo point, similar to what GetWorkingBeatmap does.
+ BeatmapInfo? detachedBeatmap = null;
+
+ beatmapSet.PerformRead(s => detachedBeatmap = s.Beatmaps.First().Detach());
+
+ BeatmapSetInfo? detachedBeatmapSet = detachedBeatmap?.BeatmapSet;
+
+ Debug.Assert(detachedBeatmapSet != null);
+
+ var newUser = new RealmUser { Username = "peppy", OnlineID = 2 };
+
+ detachedBeatmapSet.Beatmaps.First().Metadata.Artist = "New Artist";
+ detachedBeatmapSet.Beatmaps.First().Metadata.Author = newUser;
+
+ Assert.AreNotEqual(detachedBeatmapSet.Status, BeatmapOnlineStatus.Ranked);
+ detachedBeatmapSet.Status = BeatmapOnlineStatus.Ranked;
+
+ beatmapSet.PerformWrite(s =>
+ {
+ detachedBeatmapSet.CopyChangesToRealm(s);
+ });
+
+ beatmapSet.PerformRead(s =>
+ {
+ // Check above changes explicitly.
+ Assert.AreEqual(BeatmapOnlineStatus.Ranked, s.Status);
+ Assert.AreEqual("New Artist", s.Beatmaps.First().Metadata.Artist);
+ Assert.AreEqual(newUser, s.Beatmaps.First().Metadata.Author);
+ Assert.NotZero(s.Files.Count);
+
+ // Check nothing was lost in the copy operation.
+ Assert.AreEqual(s.Files.Count, detachedBeatmapSet.Files.Count);
+ Assert.AreEqual(s.Files.Select(f => f.File).Count(), detachedBeatmapSet.Files.Select(f => f.File).Count());
+ Assert.AreEqual(s.Beatmaps.Count, detachedBeatmapSet.Beatmaps.Count);
+ Assert.AreEqual(s.Beatmaps.Select(f => f.Difficulty).Count(), detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
+ Assert.AreEqual(s.Metadata, detachedBeatmapSet.Metadata);
+ });
+ }
+ });
+ }
+
[Test]
public void TestImportBeatmapThenCleanup()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using (var importer = new BeatmapImporter(realmFactory, storage))
- using (new RealmRulesetStore(realmFactory, storage))
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
{
- ILive? imported;
+ Live? imported;
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
imported = await importer.Import(reader);
- Assert.AreEqual(1, realmFactory.Context.All().Count());
+ Assert.AreEqual(1, realm.Realm.All().Count());
Assert.NotNull(imported);
Debug.Assert(imported != null);
imported.PerformWrite(s => s.DeletePending = true);
- Assert.AreEqual(1, realmFactory.Context.All().Count(s => s.DeletePending));
+ Assert.AreEqual(1, realm.Realm.All().Count(s => s.DeletePending));
}
});
Logger.Log("Running with no work to purge pending deletions");
- RunTestWithRealm((realmFactory, _) => { Assert.AreEqual(0, realmFactory.Context.All().Count()); });
+ RunTestWithRealm((realm, _) => { Assert.AreEqual(0, realm.Realm.All().Count()); });
}
[Test]
public void TestImportWhenClosed()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- await LoadOszIntoStore(importer, realmFactory.Context);
+ await LoadOszIntoStore(importer, realm.Realm);
});
}
[Test]
public void TestAccessFileAfterImport()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
var beatmap = imported.Beatmaps.First();
var file = beatmap.File;
@@ -96,33 +198,33 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDelete()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
});
}
[Test]
public void TestImportThenDeleteFromStream()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? tempPath = TestResources.GetTestBeatmapForImport();
- ILive? importedSet;
+ Live? importedSet;
using (var stream = File.OpenRead(tempPath))
{
importedSet = await importer.Import(new ImportTask(stream, Path.GetFileName(tempPath)));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
}
Assert.NotNull(importedSet);
@@ -131,39 +233,39 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
File.Delete(tempPath);
- var imported = realmFactory.Context.All().First(beatmapSet => beatmapSet.ID == importedSet.ID);
+ var imported = realm.Realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
});
}
[Test]
public void TestImportThenImport()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
- checkBeatmapSetCount(realmFactory.Context, 1);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkSingleReferencedFileCount(realm.Realm, 18);
});
}
[Test]
public void TestImportThenImportWithReZip()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -172,7 +274,7 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
string hashBefore = hashFile(temp);
@@ -190,7 +292,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -209,10 +311,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenImportWithChangedHashedFile()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -221,9 +323,9 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- await createScoreForBeatmap(realmFactory.Context, imported.Beatmaps.First());
+ await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First());
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
@@ -241,7 +343,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
// check the newly "imported" beatmap is not the original.
Assert.NotNull(importedSecondTime);
@@ -261,10 +363,10 @@ namespace osu.Game.Tests.Database
[Ignore("intentionally broken by import optimisations")]
public void TestImportThenImportWithChangedFile()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -273,7 +375,7 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
@@ -290,7 +392,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -309,10 +411,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenImportWithDifferentFilename()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -321,7 +423,7 @@ namespace osu.Game.Tests.Database
try
{
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(extractedFolder);
@@ -338,7 +440,7 @@ namespace osu.Game.Tests.Database
var importedSecondTime = await importer.Import(new ImportTask(temp));
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.NotNull(importedSecondTime);
Debug.Assert(importedSecondTime != null);
@@ -358,12 +460,12 @@ namespace osu.Game.Tests.Database
[Ignore("intentionally broken by import optimisations")]
public void TestImportCorruptThenImport()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
var firstFile = imported.Files.First();
@@ -374,7 +476,7 @@ namespace osu.Game.Tests.Database
using (var stream = storage.GetStream(firstFile.File.GetStoragePath(), FileAccess.Write, FileMode.Create))
stream.WriteByte(0);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
using (var stream = storage.GetStream(firstFile.File.GetStoragePath()))
Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
@@ -383,18 +485,18 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
- checkBeatmapSetCount(realmFactory.Context, 1);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkSingleReferencedFileCount(realm.Realm, 18);
});
}
[Test]
public void TestModelCreationFailureDoesntReturn()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
var progressNotification = new ImportProgressNotification();
@@ -408,8 +510,8 @@ namespace osu.Game.Tests.Database
new ImportTask(zipStream, string.Empty)
);
- checkBeatmapSetCount(realmFactory.Context, 0);
- checkBeatmapCount(realmFactory.Context, 0);
+ checkBeatmapSetCount(realm.Realm, 0);
+ checkBeatmapCount(realm.Realm, 0);
Assert.IsEmpty(imported);
Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
@@ -419,7 +521,7 @@ namespace osu.Game.Tests.Database
[Test]
public void TestRollbackOnFailure()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
int loggedExceptionCount = 0;
@@ -429,16 +531,16 @@ namespace osu.Game.Tests.Database
Interlocked.Increment(ref loggedExceptionCount);
};
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- realmFactory.Context.Write(() => imported.Hash += "-changed");
+ realm.Realm.Write(() => imported.Hash += "-changed");
- checkBeatmapSetCount(realmFactory.Context, 1);
- checkBeatmapCount(realmFactory.Context, 12);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkBeatmapCount(realm.Realm, 12);
+ checkSingleReferencedFileCount(realm.Realm, 18);
string? brokenTempFilename = TestResources.GetTestBeatmapForImport();
@@ -463,10 +565,10 @@ namespace osu.Game.Tests.Database
{
}
- checkBeatmapSetCount(realmFactory.Context, 1);
- checkBeatmapCount(realmFactory.Context, 12);
+ checkBeatmapSetCount(realm.Realm, 1);
+ checkBeatmapCount(realm.Realm, 12);
- checkSingleReferencedFileCount(realmFactory.Context, 18);
+ checkSingleReferencedFileCount(realm.Realm, 18);
Assert.AreEqual(1, loggedExceptionCount);
@@ -477,18 +579,18 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDeleteThenImportOptimisedPath()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
Assert.IsTrue(imported.DeletePending);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
@@ -499,20 +601,52 @@ namespace osu.Game.Tests.Database
}
[Test]
- public void TestImportThenDeleteThenImportNonOptimisedPath()
+ public void TestImportThenReimportAfterMissingFiles()
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
- using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realmFactory, storage);
+ using var store = new RulesetStore(realmFactory, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realmFactory.Realm);
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realmFactory.Realm);
Assert.IsTrue(imported.DeletePending);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ // intentionally nuke all files
+ storage.DeleteDirectory("files");
+
+ Assert.That(imported.Files.All(f => !storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath())));
+
+ var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Realm);
+
+ // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
+ Assert.IsTrue(imported.ID == importedSecondTime.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
+ Assert.IsFalse(imported.DeletePending);
+ Assert.IsFalse(importedSecondTime.DeletePending);
+
+ // check that the files now exist, even though they were deleted above.
+ Assert.That(importedSecondTime.Files.All(f => storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath())));
+ });
+ }
+
+ [Test]
+ public void TestImportThenDeleteThenImportNonOptimisedPath()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ using var importer = new NonOptimisedBeatmapImporter(realm, storage);
+ using var store = new RulesetStore(realm, storage);
+
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
+
+ deleteBeatmapSet(imported, realm.Realm);
+
+ Assert.IsTrue(imported.DeletePending);
+
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
@@ -525,22 +659,22 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDeleteThenImportWithOnlineIDsMissing()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var imported = await LoadOszIntoStore(importer, realmFactory.Context);
+ var imported = await LoadOszIntoStore(importer, realm.Realm);
- realmFactory.Context.Write(() =>
+ realm.Realm.Write(() =>
{
foreach (var b in imported.Beatmaps)
b.OnlineID = -1;
});
- deleteBeatmapSet(imported, realmFactory.Context);
+ deleteBeatmapSet(imported, realm.Realm);
- var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
+ var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
Assert.IsTrue(imported.ID != importedSecondTime.ID);
@@ -551,12 +685,12 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithDuplicateBeatmapIDs()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
- var metadata = new RealmBeatmapMetadata
+ var metadata = new BeatmapMetadata
{
Artist = "SomeArtist",
Author =
@@ -565,18 +699,18 @@ namespace osu.Game.Tests.Database
}
};
- var ruleset = realmFactory.Context.All().First();
+ var ruleset = realm.Realm.All().First();
- var toImport = new RealmBeatmapSet
+ var toImport = new BeatmapSetInfo
{
OnlineID = 1,
Beatmaps =
{
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata)
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata)
{
OnlineID = 2,
},
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata)
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata)
{
OnlineID = 2,
Status = BeatmapOnlineStatus.Loved,
@@ -584,7 +718,7 @@ namespace osu.Game.Tests.Database
}
};
- var imported = await importer.Import(toImport);
+ var imported = importer.Import(toImport);
Assert.NotNull(imported);
Debug.Assert(imported != null);
@@ -597,15 +731,15 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWhenFileOpen()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
using (File.OpenRead(temp))
await importer.Import(temp);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
File.Delete(temp);
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
});
@@ -614,10 +748,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithDuplicateHashes()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -638,7 +772,7 @@ namespace osu.Game.Tests.Database
await importer.Import(temp);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
}
finally
{
@@ -650,10 +784,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportNestedStructure()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -678,7 +812,7 @@ namespace osu.Game.Tests.Database
Assert.NotNull(imported);
Debug.Assert(imported != null);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("subfolder"))), "Files contain common subfolder");
}
@@ -692,10 +826,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithIgnoredDirectoryInArchive()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -728,7 +862,7 @@ namespace osu.Game.Tests.Database
Assert.NotNull(imported);
Debug.Assert(imported != null);
- ensureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("__MACOSX"))), "Files contain resource fork folder, which should be ignored");
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("actual_data"))), "Files contain common subfolder");
@@ -743,27 +877,27 @@ namespace osu.Game.Tests.Database
[Test]
public void TestUpdateBeatmapInfo()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapImporter(realmFactory, storage);
- using var store = new RealmRulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
await importer.Import(temp);
// Update via the beatmap, not the beatmap info, to ensure correct linking
- RealmBeatmapSet setToUpdate = realmFactory.Context.All().First();
+ BeatmapSetInfo setToUpdate = realm.Realm.All().First();
var beatmapToUpdate = setToUpdate.Beatmaps.First();
- realmFactory.Context.Write(() => beatmapToUpdate.DifficultyName = "updated");
+ realm.Realm.Write(() => beatmapToUpdate.DifficultyName = "updated");
- RealmBeatmap updatedInfo = realmFactory.Context.All().First(b => b.ID == beatmapToUpdate.ID);
+ BeatmapInfo updatedInfo = realm.Realm.All().First(b => b.ID == beatmapToUpdate.ID);
Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated"));
});
}
- public static async Task LoadQuickOszIntoOsu(BeatmapImporter importer, Realm realm)
+ public static async Task LoadQuickOszIntoOsu(BeatmapImporter importer, Realm realm)
{
string? temp = TestResources.GetQuickTestBeatmapForImport();
@@ -771,14 +905,14 @@ namespace osu.Game.Tests.Database
Assert.NotNull(importedSet);
- ensureLoaded(realm);
+ EnsureLoaded(realm);
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
- return realm.All().FirstOrDefault(beatmapSet => beatmapSet.ID == importedSet!.ID);
+ return realm.All().FirstOrDefault(beatmapSet => beatmapSet.ID == importedSet!.ID);
}
- public static async Task LoadOszIntoStore(BeatmapImporter importer, Realm realm, string? path = null, bool virtualTrack = false)
+ public static async Task LoadOszIntoStore(BeatmapImporter importer, Realm realm, string? path = null, bool virtualTrack = false)
{
string? temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
@@ -787,24 +921,24 @@ namespace osu.Game.Tests.Database
Assert.NotNull(importedSet);
Debug.Assert(importedSet != null);
- ensureLoaded(realm);
+ EnsureLoaded(realm);
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
- return realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID);
+ return realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID);
}
- private void deleteBeatmapSet(RealmBeatmapSet imported, Realm realm)
+ private void deleteBeatmapSet(BeatmapSetInfo imported, Realm realm)
{
realm.Write(() => imported.DeletePending = true);
checkBeatmapSetCount(realm, 0);
checkBeatmapSetCount(realm, 1, true);
- Assert.IsTrue(realm.All().First(_ => true).DeletePending);
+ Assert.IsTrue(realm.All().First(_ => true).DeletePending);
}
- private static Task createScoreForBeatmap(Realm realm, RealmBeatmap beatmap)
+ private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap)
{
// TODO: reimplement when we have score support in realm.
// return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
@@ -820,8 +954,8 @@ namespace osu.Game.Tests.Database
private static void checkBeatmapSetCount(Realm realm, int expected, bool includeDeletePending = false)
{
Assert.AreEqual(expected, includeDeletePending
- ? realm.All().Count()
- : realm.All().Count(s => !s.DeletePending));
+ ? realm.All().Count()
+ : realm.All().Count(s => !s.DeletePending));
}
private static string hashFile(string filename)
@@ -832,7 +966,7 @@ namespace osu.Game.Tests.Database
private static void checkBeatmapCount(Realm realm, int expected)
{
- Assert.AreEqual(expected, realm.All().Where(_ => true).ToList().Count);
+ Assert.AreEqual(expected, realm.All().Where(_ => true).ToList().Count);
}
private static void checkSingleReferencedFileCount(Realm realm, int expected)
@@ -848,26 +982,25 @@ namespace osu.Game.Tests.Database
Assert.AreEqual(expected, singleReferencedCount);
}
- private static void ensureLoaded(Realm realm, int timeout = 60000)
+ internal static void EnsureLoaded(Realm realm, int timeout = 60000)
{
- IQueryable? resultSets = null;
+ IQueryable? resultSets = null;
waitForOrAssert(() =>
- {
- realm.Refresh();
- return (resultSets = realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any();
- },
- @"BeatmapSet did not import to the database in allocated time.", timeout);
+ {
+ realm.Refresh();
+ return (resultSets = realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any();
+ }, @"BeatmapSet did not import to the database in allocated time.", timeout);
// ensure we were stored to beatmap database backing...
Assert.IsTrue(resultSets?.Count() == 1, $@"Incorrect result count found ({resultSets?.Count()} but should be 1).");
- IEnumerable queryBeatmapSets() => realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526);
+ IEnumerable queryBeatmapSets() => realm.All().Where(s => !s.DeletePending && s.OnlineID == 241526);
var set = queryBeatmapSets().First();
// ReSharper disable once PossibleUnintendedReferenceComparison
- IEnumerable queryBeatmaps() => realm.All().Where(s => s.BeatmapSet != null && s.BeatmapSet == set);
+ IEnumerable queryBeatmaps() => realm.All().Where(s => s.BeatmapSet != null && s.BeatmapSet == set);
Assert.AreEqual(12, queryBeatmaps().Count(), @"Beatmap count was not correct");
Assert.AreEqual(1, queryBeatmapSets().Count(), @"Beatmapset count was not correct");
@@ -880,7 +1013,7 @@ namespace osu.Game.Tests.Database
countBeatmaps = queryBeatmaps().Count(),
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).");
- foreach (RealmBeatmap b in set.Beatmaps)
+ foreach (BeatmapInfo b in set.Beatmaps)
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
Assert.IsTrue(set.Beatmaps.Count > 0);
}
@@ -903,8 +1036,8 @@ namespace osu.Game.Tests.Database
public class NonOptimisedBeatmapImporter : BeatmapImporter
{
- public NonOptimisedBeatmapImporter(RealmContextFactory realmFactory, Storage storage)
- : base(realmFactory, storage)
+ public NonOptimisedBeatmapImporter(RealmAccess realm, Storage storage)
+ : base(realm, storage)
{
}
diff --git a/osu.Game.Tests/Database/FileStoreTests.cs b/osu.Game.Tests/Database/FileStoreTests.cs
index 3cb4705381..98b0ed99b5 100644
--- a/osu.Game.Tests/Database/FileStoreTests.cs
+++ b/osu.Game.Tests/Database/FileStoreTests.cs
@@ -19,10 +19,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportFile()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
@@ -36,10 +36,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportSameFileTwice()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 });
@@ -53,10 +53,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestDontPurgeReferenced()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
@@ -92,10 +92,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestPurgeUnreferenced()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realmAccess, storage) =>
{
- var realm = realmFactory.Context;
- var files = new RealmFileStore(realmFactory, storage);
+ var realm = realmAccess.Realm;
+ var files = new RealmFileStore(realmAccess, storage);
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs
index 2285b22a3a..8262ef18d4 100644
--- a/osu.Game.Tests/Database/GeneralUsageTests.cs
+++ b/osu.Game.Tests/Database/GeneralUsageTests.cs
@@ -5,8 +5,8 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
+using osu.Game.Beatmaps;
using osu.Game.Database;
-using osu.Game.Models;
#nullable enable
@@ -21,15 +21,15 @@ namespace osu.Game.Tests.Database
[Test]
public void TestConstructRealm()
{
- RunTestWithRealm((realmFactory, _) => { realmFactory.CreateContext().Refresh(); });
+ RunTestWithRealm((realm, _) => { realm.Run(r => r.Refresh()); });
}
[Test]
public void TestBlockOperations()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- using (realmFactory.BlockAllOperations())
+ using (realm.BlockAllOperations())
{
}
});
@@ -42,27 +42,26 @@ namespace osu.Game.Tests.Database
[Test]
public void TestNestedContextCreationWithSubscription()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
bool callbackRan = false;
- using (var context = realmFactory.CreateContext())
+ realm.RegisterCustomSubscription(r =>
{
- var subscription = context.All().QueryAsyncWithNotifications((sender, changes, error) =>
+ var subscription = r.All().QueryAsyncWithNotifications((sender, changes, error) =>
{
- using (realmFactory.CreateContext())
+ realm.Run(_ =>
{
callbackRan = true;
- }
+ });
});
// Force the callback above to run.
- using (realmFactory.CreateContext())
- {
- }
+ realm.Run(rr => rr.Refresh());
subscription?.Dispose();
- }
+ return null;
+ });
Assert.IsTrue(callbackRan);
});
@@ -71,31 +70,36 @@ namespace osu.Game.Tests.Database
[Test]
public void TestBlockOperationsWithContention()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
ManualResetEventSlim stopThreadedUsage = new ManualResetEventSlim();
ManualResetEventSlim hasThreadedUsage = new ManualResetEventSlim();
Task.Factory.StartNew(() =>
{
- using (realmFactory.CreateContext())
+ realm.Run(_ =>
{
hasThreadedUsage.Set();
stopThreadedUsage.Wait();
- }
+ });
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler);
hasThreadedUsage.Wait();
Assert.Throws(() =>
{
- using (realmFactory.BlockAllOperations())
+ using (realm.BlockAllOperations())
{
}
});
stopThreadedUsage.Set();
+
+ // Ensure we can block a second time after the usage has ended.
+ using (realm.BlockAllOperations())
+ {
+ }
});
}
}
diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs
index 06cb5a3607..4bc1f5078a 100644
--- a/osu.Game.Tests/Database/RealmLiveTests.cs
+++ b/osu.Game.Tests/Database/RealmLiveTests.cs
@@ -6,9 +6,10 @@ using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
+using osu.Framework.Extensions;
using osu.Framework.Testing;
+using osu.Game.Beatmaps;
using osu.Game.Database;
-using osu.Game.Models;
using Realms;
#nullable enable
@@ -20,11 +21,11 @@ namespace osu.Game.Tests.Database
[Test]
public void TestLiveEquality()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(realmFactory);
+ Live beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm));
- ILive beatmap2 = realmFactory.CreateContext().All().First().ToLive(realmFactory);
+ Live beatmap2 = realm.Run(r => r.All().First().ToLive(realm));
Assert.AreEqual(beatmap, beatmap2);
});
@@ -33,26 +34,29 @@ namespace osu.Game.Tests.Database
[Test]
public void TestAccessAfterStorageMigrate()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
- ILive liveBeatmap;
+ Live? liveBeatmap = null;
- using (var context = realmFactory.CreateContext())
+ realm.Run(r =>
{
- context.Write(r => r.Add(beatmap));
+ r.Write(_ => r.Add(beatmap));
- liveBeatmap = beatmap.ToLive(realmFactory);
- }
+ liveBeatmap = beatmap.ToLive(realm);
+ });
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
{
migratedStorage.DeleteDirectory(string.Empty);
- storage.Migrate(migratedStorage);
+ using (realm.BlockAllOperations())
+ {
+ storage.Migrate(migratedStorage);
+ }
- Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
+ Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
}
});
}
@@ -60,14 +64,13 @@ namespace osu.Game.Tests.Database
[Test]
public void TestAccessAfterAttach()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
- var liveBeatmap = beatmap.ToLive(realmFactory);
+ var liveBeatmap = beatmap.ToLive(realm);
- using (var context = realmFactory.CreateContext())
- context.Write(r => r.Add(beatmap));
+ realm.Run(r => r.Write(_ => r.Add(beatmap)));
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
});
@@ -76,7 +79,7 @@ namespace osu.Game.Tests.Database
[Test]
public void TestAccessNonManaged()
{
- var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
var liveBeatmap = beatmap.ToLiveUnmanaged();
Assert.IsFalse(beatmap.Hidden);
@@ -93,18 +96,18 @@ namespace osu.Game.Tests.Database
[Test]
public void TestScopedReadWithoutContext()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- using (var threadContext = realmFactory.CreateContext())
+ realm.Run(threadContext =>
{
- var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
- }
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ liveBeatmap = beatmap.ToLive(realm);
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -115,25 +118,25 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(beatmap.IsValid);
Assert.IsFalse(beatmap.Hidden);
});
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
[Test]
public void TestScopedWriteWithoutContext()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- using (var threadContext = realmFactory.CreateContext())
+ realm.Run(threadContext =>
{
- var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
- }
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ liveBeatmap = beatmap.ToLive(realm);
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -141,17 +144,17 @@ namespace osu.Game.Tests.Database
{
liveBeatmap.PerformWrite(beatmap => { beatmap.Hidden = true; });
liveBeatmap.PerformRead(beatmap => { Assert.IsTrue(beatmap.Hidden); });
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
[Test]
public void TestValueAccessNonManaged()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
- var liveBeatmap = beatmap.ToLive(realmFactory);
+ var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
+ var liveBeatmap = beatmap.ToLive(realm);
Assert.DoesNotThrow(() =>
{
@@ -163,19 +166,19 @@ namespace osu.Game.Tests.Database
[Test]
public void TestValueAccessWithOpenContextFails()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- using (var threadContext = realmFactory.CreateContext())
+ realm.Run(threadContext =>
{
- var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
- }
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ liveBeatmap = beatmap.ToLive(realm);
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -188,32 +191,32 @@ namespace osu.Game.Tests.Database
});
// Can't be used, even from within a valid context.
- using (realmFactory.CreateContext())
+ realm.Run(threadContext =>
{
Assert.Throws(() =>
{
var __ = liveBeatmap.Value;
});
- }
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
[Test]
public void TestValueAccessWithoutOpenContextFails()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive? liveBeatmap = null;
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- using (var threadContext = realmFactory.CreateContext())
+ realm.Run(threadContext =>
{
- var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
- }
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ liveBeatmap = beatmap.ToLive(realm);
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
@@ -223,63 +226,65 @@ namespace osu.Game.Tests.Database
{
var unused = liveBeatmap.Value;
});
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
});
}
[Test]
public void TestLiveAssumptions()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
int changesTriggered = 0;
- using (var updateThreadContext = realmFactory.CreateContext())
+ realm.RegisterCustomSubscription(outerRealm =>
{
- updateThreadContext.All().QueryAsyncWithNotifications(gotChange);
- ILive? liveBeatmap = null;
+ outerRealm.All().QueryAsyncWithNotifications(gotChange);
+ Live? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- using (var threadContext = realmFactory.CreateContext())
+ realm.Run(innerRealm =>
{
var ruleset = CreateRuleset();
- var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ var beatmap = innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
// add a second beatmap to ensure that a full refresh occurs below.
// not just a refresh from the resolved Live.
- threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
+ innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
- }
- }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
+ liveBeatmap = beatmap.ToLive(realm);
+ });
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null);
// not yet seen by main context
- Assert.AreEqual(0, updateThreadContext.All().Count());
+ Assert.AreEqual(0, outerRealm.All().Count());
Assert.AreEqual(0, changesTriggered);
liveBeatmap.PerformRead(resolved =>
{
// retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point.
// ReSharper disable once AccessToDisposedClosure
- Assert.AreEqual(2, updateThreadContext.All().Count());
+ Assert.AreEqual(2, outerRealm.All().Count());
Assert.AreEqual(1, changesTriggered);
// can access properties without a crash.
Assert.IsFalse(resolved.Hidden);
// ReSharper disable once AccessToDisposedClosure
- updateThreadContext.Write(r =>
+ outerRealm.Write(r =>
{
// can use with the main context.
r.Remove(resolved);
});
});
- }
- void gotChange(IRealmCollection sender, ChangeSet changes, Exception error)
+ return null;
+ });
+
+ void gotChange(IRealmCollection sender, ChangeSet changes, Exception error)
{
changesTriggered++;
}
diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
new file mode 100644
index 0000000000..d62ce3b585
--- /dev/null
+++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
@@ -0,0 +1,138 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Tests.Resources;
+using Realms;
+
+#nullable enable
+
+namespace osu.Game.Tests.Database
+{
+ [TestFixture]
+ public class RealmSubscriptionRegistrationTests : RealmTest
+ {
+ [Test]
+ public void TestSubscriptionWithContextLoss()
+ {
+ IEnumerable? resolvedItems = null;
+ ChangeSet? lastChanges = null;
+
+ RunTestWithRealm((realm, _) =>
+ {
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ var registration = realm.RegisterForNotifications(r => r.All(), onChanged);
+
+ testEventsArriving(true);
+
+ // All normal until here.
+ // Now let's yank the main realm context.
+ resolvedItems = null;
+ lastChanges = null;
+
+ using (realm.BlockAllOperations())
+ Assert.That(resolvedItems, Is.Empty);
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ testEventsArriving(true);
+
+ // Now let's try unsubscribing.
+ resolvedItems = null;
+ lastChanges = null;
+
+ registration.Dispose();
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ testEventsArriving(false);
+
+ // And make sure even after another context loss we don't get firings.
+ using (realm.BlockAllOperations())
+ Assert.That(resolvedItems, Is.Null);
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ testEventsArriving(false);
+
+ void testEventsArriving(bool shouldArrive)
+ {
+ realm.Run(r => r.Refresh());
+
+ if (shouldArrive)
+ Assert.That(resolvedItems, Has.One.Items);
+ else
+ Assert.That(resolvedItems, Is.Null);
+
+ realm.Write(r =>
+ {
+ r.RemoveAll();
+ r.RemoveAll();
+ });
+
+ realm.Run(r => r.Refresh());
+
+ if (shouldArrive)
+ Assert.That(lastChanges?.DeletedIndices, Has.One.Items);
+ else
+ Assert.That(lastChanges, Is.Null);
+ }
+ });
+
+ void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error)
+ {
+ if (changes == null)
+ resolvedItems = sender;
+
+ lastChanges = changes;
+ }
+ }
+
+ [Test]
+ public void TestCustomRegisterWithContextLoss()
+ {
+ RunTestWithRealm((realm, _) =>
+ {
+ BeatmapSetInfo? beatmapSetInfo = null;
+
+ realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
+
+ var subscription = realm.RegisterCustomSubscription(r =>
+ {
+ beatmapSetInfo = r.All().First();
+
+ return new InvokeOnDisposal(() => beatmapSetInfo = null);
+ });
+
+ Assert.That(beatmapSetInfo, Is.Not.Null);
+
+ using (realm.BlockAllOperations())
+ {
+ // custom disposal action fired when context lost.
+ Assert.That(beatmapSetInfo, Is.Null);
+ }
+
+ // re-registration after context restore.
+ realm.Run(r => r.Refresh());
+ Assert.That(beatmapSetInfo, Is.Not.Null);
+
+ subscription.Dispose();
+
+ Assert.That(beatmapSetInfo, Is.Null);
+
+ using (realm.BlockAllOperations())
+ Assert.That(beatmapSetInfo, Is.Null);
+
+ realm.Run(r => r.Refresh());
+ Assert.That(beatmapSetInfo, Is.Null);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs
index 4e67f09dca..838759c991 100644
--- a/osu.Game.Tests/Database/RealmTest.cs
+++ b/osu.Game.Tests/Database/RealmTest.cs
@@ -9,9 +9,11 @@ using osu.Framework.Extensions;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Testing;
+using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.IO;
using osu.Game.Models;
+using osu.Game.Rulesets;
#nullable enable
@@ -28,7 +30,7 @@ namespace osu.Game.Tests.Database
storage.DeleteDirectory(string.Empty);
}
- protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "")
+ protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "")
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
{
@@ -37,22 +39,22 @@ namespace osu.Game.Tests.Database
// ReSharper disable once AccessToDisposedClosure
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
- using (var realmFactory = new RealmContextFactory(testStorage, "client"))
+ using (var realm = new RealmAccess(testStorage, "client"))
{
- Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
- testAction(realmFactory, testStorage);
+ Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
+ testAction(realm, testStorage);
- realmFactory.Dispose();
+ realm.Dispose();
- Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
- realmFactory.Compact();
- Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
+ Logger.Log($"Final database size: {getFileSize(testStorage, realm)}");
+ realm.Compact();
+ Logger.Log($"Final database size after compact: {getFileSize(testStorage, realm)}");
}
}));
}
}
- protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "")
+ protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "")
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
{
@@ -60,38 +62,38 @@ namespace osu.Game.Tests.Database
{
var testStorage = storage.GetStorageForDirectory(caller);
- using (var realmFactory = new RealmContextFactory(testStorage, "client"))
+ using (var realm = new RealmAccess(testStorage, "client"))
{
- Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
- await testAction(realmFactory, testStorage);
+ Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
+ await testAction(realm, testStorage);
- realmFactory.Dispose();
+ realm.Dispose();
- Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
- realmFactory.Compact();
+ Logger.Log($"Final database size: {getFileSize(testStorage, realm)}");
+ realm.Compact();
}
}));
}
}
- protected static RealmBeatmapSet CreateBeatmapSet(RealmRuleset ruleset)
+ protected static BeatmapSetInfo CreateBeatmapSet(RulesetInfo ruleset)
{
RealmFile createRealmFile() => new RealmFile { Hash = Guid.NewGuid().ToString().ComputeSHA2Hash() };
- var metadata = new RealmBeatmapMetadata
+ var metadata = new BeatmapMetadata
{
Title = "My Love",
Artist = "Kuba Oms"
};
- var beatmapSet = new RealmBeatmapSet
+ var beatmapSet = new BeatmapSetInfo
{
Beatmaps =
{
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Easy", },
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Normal", },
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Hard", },
- new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), metadata) { DifficultyName = "Insane", }
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Easy", },
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Normal", },
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Hard", },
+ new BeatmapInfo(ruleset, new BeatmapDifficulty(), metadata) { DifficultyName = "Insane", }
},
Files =
{
@@ -111,8 +113,8 @@ namespace osu.Game.Tests.Database
return beatmapSet;
}
- protected static RealmRuleset CreateRuleset() =>
- new RealmRuleset(0, "osu!", "osu", true);
+ protected static RulesetInfo CreateRuleset() =>
+ new RulesetInfo("osu", "osu!", string.Empty, 0) { Available = true };
private class RealmTestGame : Framework.Game
{
@@ -136,11 +138,11 @@ namespace osu.Game.Tests.Database
}
}
- private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory)
+ private static long getFileSize(Storage testStorage, RealmAccess realm)
{
try
{
- using (var stream = testStorage.GetStream(realmFactory.Filename))
+ using (var stream = testStorage.GetStream(realm.Filename))
return stream?.Length ?? 0;
}
catch
diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs
index cc7e8a0c97..7544142b70 100644
--- a/osu.Game.Tests/Database/RulesetStoreTests.cs
+++ b/osu.Game.Tests/Database/RulesetStoreTests.cs
@@ -3,8 +3,7 @@
using System.Linq;
using NUnit.Framework;
-using osu.Game.Models;
-using osu.Game.Stores;
+using osu.Game.Rulesets;
namespace osu.Game.Tests.Database
{
@@ -13,37 +12,37 @@ namespace osu.Game.Tests.Database
[Test]
public void TestCreateStore()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var rulesets = new RealmRulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realm, storage);
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
- Assert.AreEqual(4, realmFactory.Context.All().Count());
+ Assert.AreEqual(4, realm.Realm.All().Count());
});
}
[Test]
public void TestCreateStoreTwiceDoesntAddRulesetsAgain()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var rulesets = new RealmRulesetStore(realmFactory, storage);
- var rulesets2 = new RealmRulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realm, storage);
+ var rulesets2 = new RulesetStore(realm, storage);
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First());
- Assert.AreEqual(4, realmFactory.Context.All().Count());
+ Assert.AreEqual(4, realm.Realm.All().Count());
});
}
[Test]
public void TestRetrievedRulesetsAreDetached()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var rulesets = new RealmRulesetStore(realmFactory, storage);
+ var rulesets = new RulesetStore(realm, storage);
Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
index f05d9ab3dc..891801865f 100644
--- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
+++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Database
private RealmKeyBindingStore keyBindingStore;
- private RealmContextFactory realmContextFactory;
+ private RealmAccess realm;
[SetUp]
public void SetUp()
@@ -33,8 +33,8 @@ namespace osu.Game.Tests.Database
storage = new NativeStorage(directory.FullName);
- realmContextFactory = new RealmContextFactory(storage, "test");
- keyBindingStore = new RealmKeyBindingStore(realmContextFactory, new ReadableKeyCombinationProvider());
+ realm = new RealmAccess(storage, "test");
+ keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider());
}
[Test]
@@ -60,29 +60,12 @@ namespace osu.Game.Tests.Database
KeyBindingContainer testContainer = new TestKeyBindingContainer();
// Add some excess bindings for an action which only supports 1.
- using (var realm = realmContextFactory.CreateContext())
- using (var transaction = realm.BeginWrite())
+ realm.Write(r =>
{
- realm.Add(new RealmKeyBinding
- {
- Action = GlobalAction.Back,
- KeyCombination = new KeyCombination(InputKey.A)
- });
-
- realm.Add(new RealmKeyBinding
- {
- Action = GlobalAction.Back,
- KeyCombination = new KeyCombination(InputKey.S)
- });
-
- realm.Add(new RealmKeyBinding
- {
- Action = GlobalAction.Back,
- KeyCombination = new KeyCombination(InputKey.D)
- });
-
- transaction.Commit();
- }
+ r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A)));
+ r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S)));
+ r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D)));
+ });
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
@@ -93,13 +76,13 @@ namespace osu.Game.Tests.Database
private int queryCount(GlobalAction? match = null)
{
- using (var realm = realmContextFactory.CreateContext())
+ return realm.Run(r =>
{
- var results = realm.All();
+ var results = r.All();
if (match.HasValue)
results = results.Where(k => k.ActionInt == (int)match.Value);
return results.Count();
- }
+ });
}
[Test]
@@ -109,32 +92,32 @@ namespace osu.Game.Tests.Database
keyBindingStore.Register(testContainer, Enumerable.Empty());
- using (var primaryRealm = realmContextFactory.CreateContext())
+ realm.Run(outerRealm =>
{
- var backBinding = primaryRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back);
+ var backBinding = outerRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back);
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape }));
var tsr = ThreadSafeReference.Create(backBinding);
- using (var threadedContext = realmContextFactory.CreateContext())
+ realm.Run(innerRealm =>
{
- var binding = threadedContext.ResolveReference(tsr);
- threadedContext.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace));
- }
+ var binding = innerRealm.ResolveReference(tsr);
+ innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace));
+ });
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
// check still correct after re-query.
- backBinding = primaryRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back);
+ backBinding = outerRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back);
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
- }
+ });
}
[TearDown]
public void TearDown()
{
- realmContextFactory.Dispose();
+ realm.Dispose();
storage.DeleteDirectory(string.Empty);
}
diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
index f9b7bfa586..614b9b4ac1 100644
--- a/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
+++ b/osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs
@@ -74,7 +74,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestMissingFile()
{
- beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
+ beatmap.BeatmapInfo.BeatmapSet?.Files.Clear();
var issues = check.Run(getContext(null)).ToList();
diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs
index f36454aa71..01baaadc7d 100644
--- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs
+++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestBackgroundSetAndNotInFiles()
{
- beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
+ beatmap.BeatmapInfo.BeatmapSet?.Files.Clear();
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();
diff --git a/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs b/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs
index f702921986..9067714ff9 100644
--- a/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs
+++ b/osu.Game.Tests/Editing/Checks/CheckTestHelpers.cs
@@ -1,18 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Beatmaps;
-using osu.Game.IO;
+using osu.Game.Models;
namespace osu.Game.Tests.Editing.Checks
{
public static class CheckTestHelpers
{
- public static BeatmapSetFileInfo CreateMockFile(string extension) =>
- new BeatmapSetFileInfo
- {
- Filename = $"abc123.{extension}",
- FileInfo = new FileInfo { Hash = "abcdef" }
- };
+ public static RealmNamedFileUsage CreateMockFile(string extension) =>
+ new RealmNamedFileUsage(new RealmFile { Hash = "abcdef" }, $"abc123.{extension}");
}
}
diff --git a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
index 8adf0d3764..242fec2f68 100644
--- a/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
+++ b/osu.Game.Tests/Editing/Checks/CheckTooShortAudioFilesTest.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Diagnostics;
using System.IO;
using System.Linq;
using ManagedBass;
@@ -45,6 +46,8 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestDifferentExtension()
{
+ Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null);
+
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg"));
diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs
index 481cb3230e..2d61948a2a 100644
--- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs
+++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs
@@ -2,7 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
@@ -158,7 +159,13 @@ namespace osu.Game.Tests.Editing
private (EditorChangeHandler, EditorBeatmap) createChangeHandler()
{
- var beatmap = new EditorBeatmap(new Beatmap());
+ var beatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ });
var changeHandler = new EditorChangeHandler(beatmap);
diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
index 8eb9452736..43f22e4e90 100644
--- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
+++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
@@ -35,7 +35,13 @@ namespace osu.Game.Tests.Editing
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap()),
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ }),
Content = new Container
{
RelativeSizeAxes = Axes.Both,
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index f0ebd7a8cc..88862ea28b 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -261,7 +261,7 @@ namespace osu.Game.Tests.Gameplay
public AudioManager AudioManager => Audio;
public IResourceStore Files => null;
public new IResourceStore Resources => base.Resources;
- public RealmContextFactory RealmContextFactory => null;
+ public RealmAccess RealmAccess => null;
public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null;
#endregion
diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs
index dbeb453d4d..a658a0eaeb 100644
--- a/osu.Game.Tests/ImportTest.cs
+++ b/osu.Game.Tests/ImportTest.cs
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Game.Collections;
using osu.Game.Tests.Resources;
@@ -58,7 +59,7 @@ namespace osu.Game.Tests
{
// Beatmap must be imported before the collection manager is loaded.
if (withBeatmap)
- BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
+ BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely();
AddInternal(CollectionManager = new CollectionManager(Storage));
}
diff --git a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
index 834c05fd08..6ae8231deb 100644
--- a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
+++ b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Tests.NonVisual
const int beat_length_numerator = 2000;
const int beat_length_denominator = 7;
- const TimeSignatures signature = TimeSignatures.SimpleQuadruple;
+ TimeSignature signature = TimeSignature.SimpleQuadruple;
var beatmap = new Beatmap
{
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.NonVisual
for (int i = 0; i * beat_length_denominator < barLines.Count; i++)
{
var barLine = barLines[i * beat_length_denominator];
- int expectedTime = beat_length_numerator * (int)signature * i;
+ int expectedTime = beat_length_numerator * signature.Numerator * i;
// every seventh bar's start time should be at least greater than the whole number we expect.
// It cannot be less, as that can affect overlapping scroll algorithms
@@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual
Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime));
// check major/minor lines for good measure too
- Assert.AreEqual(i % (int)signature == 0, barLine.Major);
+ Assert.AreEqual(i % signature.Numerator == 0, barLine.Major);
}
}
diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs
index 534983f869..1b6049fcb7 100644
--- a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs
+++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
@@ -23,8 +24,10 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestDatabasedWithDatabased()
{
- var ourInfo = new BeatmapSetInfo { ID = 123 };
- var otherInfo = new BeatmapSetInfo { ID = 123 };
+ var guid = Guid.NewGuid();
+
+ var ourInfo = new BeatmapSetInfo { ID = guid };
+ var otherInfo = new BeatmapSetInfo { ID = guid };
Assert.AreEqual(ourInfo, otherInfo);
}
@@ -32,7 +35,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestDatabasedWithOnline()
{
- var ourInfo = new BeatmapSetInfo { ID = 123, OnlineID = 12 };
+ var ourInfo = new BeatmapSetInfo { ID = Guid.NewGuid(), OnlineID = 12 };
var otherInfo = new BeatmapSetInfo { OnlineID = 12 };
Assert.AreNotEqual(ourInfo, otherInfo);
diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
index 4bb54f1625..834930a05e 100644
--- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
+++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
@@ -142,19 +142,28 @@ namespace osu.Game.Tests.NonVisual
Assert.That(osuStorage, Is.Not.Null);
+ // In the following tests, realm files are ignored as
+ // - in the case of checking the source, interacting with the pipe files (client.realm.note) may
+ // lead to unexpected behaviour.
+ // - in the case of checking the destination, the files may have already been recreated by the game
+ // as part of the standard migration flow.
+
foreach (string file in osuStorage.IgnoreFiles)
{
- // avoid touching realm files which may be a pipe and break everything.
- // this is also done locally inside OsuStorage via the IgnoreFiles list.
- if (file.EndsWith(".ini", StringComparison.Ordinal))
+ if (!file.Contains("realm", StringComparison.Ordinal))
+ {
Assert.That(File.Exists(Path.Combine(originalDirectory, file)));
- Assert.That(storage.Exists(file), Is.False);
+ Assert.That(storage.Exists(file), Is.False, () => $"{file} exists in destination when it was expected to be ignored");
+ }
}
foreach (string dir in osuStorage.IgnoreDirectories)
{
- Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir)));
- Assert.That(storage.ExistsDirectory(dir), Is.False);
+ if (!dir.Contains("realm", StringComparison.Ordinal))
+ {
+ Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir)));
+ Assert.That(storage.Exists(dir), Is.False, () => $"{dir} exists in destination when it was expected to be ignored");
+ }
}
Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}"));
@@ -179,7 +188,7 @@ namespace osu.Game.Tests.NonVisual
{
var osu = LoadOsuIntoHost(host);
- const string database_filename = "client.db";
+ const string database_filename = "client.realm";
Assert.DoesNotThrow(() => osu.Migrate(customPath));
Assert.That(File.Exists(Path.Combine(customPath, database_filename)));
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
index 55378043e6..33204d33a7 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
@@ -16,9 +16,13 @@ namespace osu.Game.Tests.NonVisual.Filtering
{
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
{
- Ruleset = new RulesetInfo { OnlineID = 5 },
+ Ruleset = new RulesetInfo
+ {
+ ShortName = "osu",
+ OnlineID = 0
+ },
StarRating = 4.0d,
- BaseDifficulty = new BeatmapDifficulty
+ Difficulty = new BeatmapDifficulty
{
ApproachRate = 5.0f,
DrainRate = 3.0f,
@@ -30,7 +34,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
ArtistUnicode = "check unicode too",
Title = "Title goes here",
TitleUnicode = "Title goes here",
- AuthorString = "The Author",
+ Author = { Username = "The Author" },
Source = "unit tests",
Tags = "look for tags too",
},
@@ -57,7 +61,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
- Ruleset = new RulesetInfo { OnlineID = 6 }
+ Ruleset = new RulesetInfo { ShortName = "catch" }
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
@@ -78,6 +82,20 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.IsFalse(carouselItem.Filtered.Value);
}
+ [Test]
+ public void TestCriteriaMatchingConvertedBeatmapsForCustomRulesets()
+ {
+ var exampleBeatmapInfo = getExampleBeatmap();
+ var criteria = new FilterCriteria
+ {
+ Ruleset = new RulesetInfo { OnlineID = -1 },
+ AllowConvertedBeatmaps = true
+ };
+ var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
+ carouselItem.Filter(criteria);
+ Assert.IsFalse(carouselItem.Filtered.Value);
+ }
+
[Test]
[TestCase(true)]
[TestCase(false)]
diff --git a/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs
new file mode 100644
index 0000000000..ae999d08d5
--- /dev/null
+++ b/osu.Game.Tests/NonVisual/RulesetInfoOrderingTest.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Osu;
+
+namespace osu.Game.Tests.NonVisual
+{
+ [TestFixture]
+ public class RulesetInfoOrderingTest
+ {
+ [Test]
+ public void TestOrdering()
+ {
+ var rulesets = new[]
+ {
+ new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1),
+ new OsuRuleset().RulesetInfo,
+ new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1),
+ new RulesetInfo("custom2", "Custom Ruleset 2", string.Empty, -1),
+ new CatchRuleset().RulesetInfo,
+ new RulesetInfo("custom3", "Custom Ruleset 3", string.Empty, -1),
+ };
+
+ var orderedRulesets = rulesets.OrderBy(r => r);
+
+ // Ensure all customs are after official.
+ Assert.That(orderedRulesets.Select(r => r.OnlineID), Is.EqualTo(new[] { 0, 2, -1, -1, -1, -1 }));
+
+ // Ensure customs are grouped next to each other (ie. stably sorted).
+ Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom2").Skip(1).First().ShortName, Is.EqualTo("custom2"));
+ Assert.That(orderedRulesets.SkipWhile(r => r.ShortName != "custom3").Skip(1).First().ShortName, Is.EqualTo("custom3"));
+ }
+ }
+}
diff --git a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs
index 6e5718cd4c..41b08a9e98 100644
--- a/osu.Game.Tests/NonVisual/ScoreInfoTest.cs
+++ b/osu.Game.Tests/NonVisual/ScoreInfoTest.cs
@@ -2,6 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Game.Online.API;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
@@ -22,12 +26,52 @@ namespace osu.Game.Tests.NonVisual
score.Statistics[HitResult.Good]++;
score.Rank = ScoreRank.X;
+ score.RealmUser.Username = "test";
Assert.That(scoreCopy.Statistics[HitResult.Good], Is.EqualTo(10));
Assert.That(score.Statistics[HitResult.Good], Is.EqualTo(11));
Assert.That(scoreCopy.Rank, Is.EqualTo(ScoreRank.B));
Assert.That(score.Rank, Is.EqualTo(ScoreRank.X));
+
+ Assert.That(scoreCopy.RealmUser.Username, Is.Empty);
+ Assert.That(score.RealmUser.Username, Is.EqualTo("test"));
+ }
+
+ [Test]
+ public void TestModsInitiallyEmpty()
+ {
+ var score = new ScoreInfo();
+
+ Assert.That(score.Mods, Is.Empty);
+ Assert.That(score.APIMods, Is.Empty);
+ Assert.That(score.ModsJson, Is.Empty);
+ }
+
+ [Test]
+ public void TestModsUpdatedCorrectly()
+ {
+ var score = new ScoreInfo
+ {
+ Mods = new Mod[] { new ManiaModClassic() },
+ Ruleset = new ManiaRuleset().RulesetInfo,
+ };
+
+ Assert.That(score.Mods, Contains.Item(new ManiaModClassic()));
+ Assert.That(score.APIMods, Contains.Item(new APIMod(new ManiaModClassic())));
+ Assert.That(score.ModsJson, Contains.Substring("CL"));
+
+ score.APIMods = new[] { new APIMod(new ManiaModDoubleTime()) };
+
+ Assert.That(score.Mods, Contains.Item(new ManiaModDoubleTime()));
+ Assert.That(score.APIMods, Contains.Item(new APIMod(new ManiaModDoubleTime())));
+ Assert.That(score.ModsJson, Contains.Substring("DT"));
+
+ score.Mods = new Mod[] { new ManiaModClassic() };
+
+ Assert.That(score.Mods, Contains.Item(new ManiaModClassic()));
+ Assert.That(score.APIMods, Contains.Item(new APIMod(new ManiaModClassic())));
+ Assert.That(score.ModsJson, Contains.Substring("CL"));
}
}
}
diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs
index d83eaafe20..3678279035 100644
--- a/osu.Game.Tests/NonVisual/TaskChainTest.cs
+++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs
@@ -4,6 +4,7 @@
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
+using osu.Framework.Extensions;
using osu.Game.Utils;
namespace osu.Game.Tests.NonVisual
@@ -42,9 +43,9 @@ namespace osu.Game.Tests.NonVisual
await Task.WhenAll(task1.task, task2.task, task3.task);
- Assert.That(task1.task.Result, Is.EqualTo(1));
- Assert.That(task2.task.Result, Is.EqualTo(2));
- Assert.That(task3.task.Result, Is.EqualTo(3));
+ Assert.That(task1.task.GetResultSafely(), Is.EqualTo(1));
+ Assert.That(task2.task.GetResultSafely(), Is.EqualTo(2));
+ Assert.That(task3.task.GetResultSafely(), Is.EqualTo(3));
}
[Test]
@@ -68,9 +69,9 @@ namespace osu.Game.Tests.NonVisual
// Wait on both tasks.
await Task.WhenAll(task1.task, task3.task);
- Assert.That(task1.task.Result, Is.EqualTo(1));
+ Assert.That(task1.task.GetResultSafely(), Is.EqualTo(1));
Assert.That(task2.task.IsCompleted, Is.False);
- Assert.That(task3.task.Result, Is.EqualTo(2));
+ Assert.That(task3.task.GetResultSafely(), Is.EqualTo(2));
}
[Test]
diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs
index 2ec5b778d1..855de9b656 100644
--- a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs
+++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Tests.Online.Chat
}
[Test]
- public void TestContainsUsernameBetweenInterpunction()
+ public void TestContainsUsernameBetweenPunctuation()
{
Assert.IsTrue(MessageNotifier.CheckContainsUsername("Hello 'test'-message", "Test"));
}
diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
index 4b160e1d67..1b7a7656b5 100644
--- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
+++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
@@ -9,10 +9,12 @@ using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.API;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Solo;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
@@ -93,7 +95,11 @@ namespace osu.Game.Tests.Online
[Test]
public void TestDeserialiseSubmittableScoreWithEmptyMods()
{
- var score = new SubmittableScore(new ScoreInfo());
+ var score = new SubmittableScore(new ScoreInfo
+ {
+ User = new APIUser(),
+ Ruleset = new OsuRuleset().RulesetInfo,
+ });
var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score));
@@ -105,7 +111,9 @@ namespace osu.Game.Tests.Online
{
var score = new SubmittableScore(new ScoreInfo
{
- Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }
+ Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } },
+ User = new APIUser(),
+ Ruleset = new OsuRuleset().RulesetInfo,
});
var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score));
diff --git a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs
index 4e77973655..ad9ea79646 100644
--- a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs
+++ b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs
@@ -5,6 +5,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Models;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Notifications;
using osu.Game.Tests.Visual;
@@ -20,13 +21,19 @@ namespace osu.Game.Tests.Online
private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo
{
OnlineID = 1,
- Metadata = new BeatmapMetadata
+ Beatmaps =
{
- Artist = "test author",
- Title = "test title",
- Author = new APIUser
+ new BeatmapInfo
{
- Username = "mapper"
+ Metadata = new BeatmapMetadata
+ {
+ Artist = "test author",
+ Title = "test title",
+ Author = new RealmUser
+ {
+ Username = "mapper"
+ }
+ }
}
}
};
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index 239c787349..f9161816e7 100644
--- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
@@ -45,8 +45,8 @@ namespace osu.Game.Tests.Online
[BackgroundDependencyLoader]
private void load(AudioManager audio, GameHost host)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host));
}
@@ -60,9 +60,8 @@ namespace osu.Game.Tests.Online
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
testBeatmapSet = testBeatmapInfo.BeatmapSet;
- var existing = beatmaps.QueryBeatmapSet(s => s.OnlineID == testBeatmapSet.OnlineID);
- if (existing != null)
- beatmaps.Delete(existing);
+ Realm.Write(r => r.RemoveAll());
+ Realm.Write(r => r.RemoveAll());
selectedItem.Value = new PlaylistItem
{
@@ -92,7 +91,7 @@ namespace osu.Game.Tests.Online
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
- AddUntilStep("wait for import", () => beatmaps.CurrentImportTask?.IsCompleted == true);
+ AddUntilStep("wait for import", () => beatmaps.CurrentImport != null);
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
}
@@ -100,13 +99,13 @@ namespace osu.Game.Tests.Online
public void TestTrackerRespectsSoftDeleting()
{
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
- AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
+ AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
- AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)));
+ AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)!.Value));
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
- AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)));
+ AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)!.Value));
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
}
@@ -114,12 +113,12 @@ namespace osu.Game.Tests.Online
public void TestTrackerRespectsChecksum()
{
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
- AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
+ AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
AddStep("import altered beatmap", () =>
{
- beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait();
+ beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).WaitSafely();
});
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
@@ -129,7 +128,7 @@ namespace osu.Game.Tests.Online
});
addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded);
- AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait());
+ AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely());
addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable);
}
@@ -154,7 +153,6 @@ namespace osu.Game.Tests.Online
Debug.Assert(info.BeatmapSet != null);
info.BeatmapSet.Beatmaps.Add(info);
- info.BeatmapSet.Metadata = info.Metadata;
info.MD5Hash = stream.ComputeMD5Hash();
info.Hash = stream.ComputeSHA2Hash();
}
@@ -166,32 +164,32 @@ namespace osu.Game.Tests.Online
{
public TaskCompletionSource AllowImport = new TaskCompletionSource();
- public Task> CurrentImportTask { get; private set; }
+ public Live CurrentImport { get; private set; }
- public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null)
- : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap)
+ public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null)
+ : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap)
{
}
- protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host)
+ protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
{
- return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, api, host);
+ return new TestBeatmapModelManager(this, storage, realm, rulesets, onlineLookupQueue);
}
internal class TestBeatmapModelManager : BeatmapModelManager
{
private readonly TestBeatmapManager testBeatmapManager;
- public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, IDatabaseContextFactory databaseContextFactory, RulesetStore rulesetStore, IAPIProvider apiProvider, GameHost gameHost)
- : base(storage, databaseContextFactory, rulesetStore, gameHost)
+ public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
+ : base(databaseAccess, storage, beatmapOnlineLookupQueue)
{
this.testBeatmapManager = testBeatmapManager;
}
- public override async Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public override Live Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{
- await testBeatmapManager.AllowImport.Task.ConfigureAwait(false);
- return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false);
+ testBeatmapManager.AllowImport.Task.WaitSafely();
+ return (testBeatmapManager.CurrentImport = base.Import(item, archive, lowPriority, cancellationToken));
}
}
}
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index 445394fc77..81b624f908 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -80,7 +80,10 @@ namespace osu.Game.Tests.Resources
public static BeatmapSetInfo CreateTestBeatmapSetInfo(int? difficultyCount = null, RulesetInfo[] rulesets = null)
{
int j = 0;
- RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length] ?? new OsuRuleset().RulesetInfo;
+
+ rulesets ??= new[] { new OsuRuleset().RulesetInfo };
+
+ RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length];
int setId = Interlocked.Increment(ref importId);
@@ -89,7 +92,7 @@ namespace osu.Game.Tests.Resources
// Create random metadata, then we can check if sorting works based on these
Artist = "Some Artist " + RNG.Next(0, 9),
Title = $"Some Song (set id {setId}) {Guid.NewGuid()}",
- AuthorString = "Some Guy " + RNG.Next(0, 9),
+ Author = { Username = "Some Guy " + RNG.Next(0, 9) },
};
var beatmapSet = new BeatmapSetInfo
@@ -97,7 +100,6 @@ namespace osu.Game.Tests.Resources
OnlineID = setId,
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
DateAdded = DateTimeOffset.UtcNow,
- Metadata = metadata
};
foreach (var b in getBeatmaps(difficultyCount ?? RNG.Next(1, 20)))
@@ -131,10 +133,10 @@ namespace osu.Game.Tests.Resources
StarRating = diff,
Length = length,
BPM = bpm,
+ Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Ruleset = rulesetInfo,
- RulesetID = rulesetInfo.ID ?? -1,
Metadata = metadata,
- BaseDifficulty = new BeatmapDifficulty
+ Difficulty = new BeatmapDifficulty
{
OverallDifficulty = diff,
}
@@ -166,7 +168,6 @@ namespace osu.Game.Tests.Resources
},
BeatmapInfo = beatmap,
Ruleset = beatmap.Ruleset,
- RulesetID = beatmap.Ruleset.ID ?? 0,
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
TotalScore = 2845370,
Accuracy = 0.95,
diff --git a/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu b/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu
new file mode 100644
index 0000000000..23732aef8c
--- /dev/null
+++ b/osu.Game.Tests/Resources/approach-rate-after-overall-difficulty.osu
@@ -0,0 +1,3 @@
+[Difficulty]
+OverallDifficulty:1
+ApproachRate:9
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu b/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu
new file mode 100644
index 0000000000..18885c6624
--- /dev/null
+++ b/osu.Game.Tests/Resources/approach-rate-before-overall-difficulty.osu
@@ -0,0 +1,3 @@
+[Difficulty]
+ApproachRate:9
+OverallDifficulty:1
\ No newline at end of file
diff --git a/osu.Game.Tests/Resources/undefined-approach-rate.osu b/osu.Game.Tests/Resources/undefined-approach-rate.osu
new file mode 100644
index 0000000000..0de24238bf
--- /dev/null
+++ b/osu.Game.Tests/Resources/undefined-approach-rate.osu
@@ -0,0 +1,2 @@
+[Difficulty]
+OverallDifficulty:1
\ No newline at end of file
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index bbc92b7817..8de9f0a292 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -5,11 +5,10 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Platform;
-using osu.Game.Beatmaps;
using osu.Game.IO.Archives;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Mods;
@@ -17,13 +16,15 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
+using osu.Game.Tests.Beatmaps.IO;
+using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Scores.IO
{
public class ImportScoreTest : ImportTest
{
[Test]
- public async Task TestBasicImport()
+ public void TestBasicImport()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -31,6 +32,8 @@ namespace osu.Game.Tests.Scores.IO
{
var osu = LoadOsuIntoHost(host, true);
+ var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
+
var toImport = new ScoreInfo
{
Rank = ScoreRank.B,
@@ -41,15 +44,16 @@ namespace osu.Game.Tests.Scores.IO
User = new APIUser { Username = "Test user" },
Date = DateTimeOffset.Now,
OnlineID = 12345,
+ Ruleset = new OsuRuleset().RulesetInfo,
+ BeatmapInfo = beatmap.Beatmaps.First()
};
- var imported = await LoadScoreIntoOsu(osu, toImport);
+ var imported = LoadScoreIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Rank, imported.Rank);
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
Assert.AreEqual(toImport.Accuracy, imported.Accuracy);
Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo);
- Assert.AreEqual(toImport.Combo, imported.Combo);
Assert.AreEqual(toImport.User.Username, imported.User.Username);
Assert.AreEqual(toImport.Date, imported.Date);
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
@@ -62,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public async Task TestImportMods()
+ public void TestImportMods()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -70,12 +74,17 @@ namespace osu.Game.Tests.Scores.IO
{
var osu = LoadOsuIntoHost(host, true);
+ var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
+
var toImport = new ScoreInfo
{
+ User = new APIUser { Username = "Test user" },
+ BeatmapInfo = beatmap.Beatmaps.First(),
+ Ruleset = new OsuRuleset().RulesetInfo,
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
};
- var imported = await LoadScoreIntoOsu(osu, toImport);
+ var imported = LoadScoreIntoOsu(osu, toImport);
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
@@ -88,7 +97,7 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public async Task TestImportStatistics()
+ public void TestImportStatistics()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -96,8 +105,13 @@ namespace osu.Game.Tests.Scores.IO
{
var osu = LoadOsuIntoHost(host, true);
+ var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
+
var toImport = new ScoreInfo
{
+ User = new APIUser { Username = "Test user" },
+ BeatmapInfo = beatmap.Beatmaps.First(),
+ Ruleset = new OsuRuleset().RulesetInfo,
Statistics = new Dictionary
{
{ HitResult.Perfect, 100 },
@@ -105,7 +119,7 @@ namespace osu.Game.Tests.Scores.IO
}
};
- var imported = await LoadScoreIntoOsu(osu, toImport);
+ var imported = LoadScoreIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]);
Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]);
@@ -118,7 +132,7 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public async Task TestImportWithDeletedBeatmapSet()
+ public void TestOnlineScoreIsAvailableLocally()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -126,49 +140,25 @@ namespace osu.Game.Tests.Scores.IO
{
var osu = LoadOsuIntoHost(host, true);
- var toImport = new ScoreInfo
+ var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
+
+ LoadScoreIntoOsu(osu, new ScoreInfo
{
- Hash = Guid.NewGuid().ToString(),
- Statistics = new Dictionary
- {
- { HitResult.Perfect, 100 },
- { HitResult.Miss, 50 }
- }
- };
-
- var imported = await LoadScoreIntoOsu(osu, toImport);
-
- var beatmapManager = osu.Dependencies.Get();
- var scoreManager = osu.Dependencies.Get();
-
- beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.BeatmapInfo.ID)));
- Assert.That(scoreManager.Query(s => s.Equals(imported)).DeletePending, Is.EqualTo(true));
-
- var secondImport = await LoadScoreIntoOsu(osu, imported);
- Assert.That(secondImport, Is.Null);
- }
- finally
- {
- host.Exit();
- }
- }
- }
-
- [Test]
- public async Task TestOnlineScoreIsAvailableLocally()
- {
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
- {
- try
- {
- var osu = LoadOsuIntoHost(host, true);
-
- await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineID = 2 }, new TestArchiveReader());
+ User = new APIUser { Username = "Test user" },
+ BeatmapInfo = beatmap.Beatmaps.First(),
+ Ruleset = new OsuRuleset().RulesetInfo,
+ OnlineID = 2
+ }, new TestArchiveReader());
var scoreManager = osu.Dependencies.Get();
// Note: A new score reference is used here since the import process mutates the original object to set an ID
- Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineID = 2 }));
+ Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo
+ {
+ User = new APIUser { Username = "Test user" },
+ BeatmapInfo = beatmap.Beatmaps.First(),
+ OnlineID = 2
+ }));
}
finally
{
@@ -177,17 +167,16 @@ namespace osu.Game.Tests.Scores.IO
}
}
- public static async Task LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
+ public static ScoreInfo LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
{
- var beatmapManager = osu.Dependencies.Get();
-
- score.BeatmapInfo ??= beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
- score.Ruleset ??= new OsuRuleset().RulesetInfo;
+ // clone to avoid attaching the input score to realm.
+ score = score.DeepClone();
var scoreManager = osu.Dependencies.Get();
- await scoreManager.Import(score, archive);
- return scoreManager.GetAllUsableScores().FirstOrDefault();
+ scoreManager.Import(score, archive);
+
+ return scoreManager.Query(_ => true);
}
internal class TestArchiveReader : ArchiveReader
diff --git a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs
index 42fcb3acab..f898774ce6 100644
--- a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs
+++ b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using NUnit.Framework;
using osu.Game.Scoring;
@@ -29,8 +30,8 @@ namespace osu.Game.Tests.Scores.IO
[Test]
public void TestNonMatchingByPrimaryKey()
{
- ScoreInfo score1 = new ScoreInfo { ID = 1 };
- ScoreInfo score2 = new ScoreInfo { ID = 2 };
+ ScoreInfo score1 = new ScoreInfo { ID = Guid.NewGuid() };
+ ScoreInfo score2 = new ScoreInfo { ID = Guid.NewGuid() };
Assert.That(score1, Is.Not.EqualTo(score2));
}
@@ -38,8 +39,10 @@ namespace osu.Game.Tests.Scores.IO
[Test]
public void TestMatchingByPrimaryKey()
{
- ScoreInfo score1 = new ScoreInfo { ID = 1 };
- ScoreInfo score2 = new ScoreInfo { ID = 1 };
+ Guid id = Guid.NewGuid();
+
+ ScoreInfo score1 = new ScoreInfo { ID = id };
+ ScoreInfo score2 = new ScoreInfo { ID = id };
Assert.That(score1, Is.EqualTo(score2));
}
diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
index 40d2455106..9b0facd625 100644
--- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
+++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
@@ -8,6 +8,7 @@ using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Game.Database;
using osu.Game.IO;
@@ -187,7 +188,7 @@ namespace osu.Game.Tests.Skins.IO
var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk"));
- imported.Result.PerformRead(s =>
+ imported.GetResultSafely().PerformRead(s =>
{
Assert.IsFalse(s.Protected);
Assert.AreNotEqual(originalSkinId, s.ID);
@@ -222,7 +223,7 @@ namespace osu.Game.Tests.Skins.IO
var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk"));
- imported.Result.PerformRead(s =>
+ imported.GetResultSafely().PerformRead(s =>
{
Assert.IsFalse(s.Protected);
Assert.AreNotEqual(originalSkinId, s.ID);
@@ -234,7 +235,7 @@ namespace osu.Game.Tests.Skins.IO
#endregion
- private void assertCorrectMetadata(ILive import1, string name, string creator, OsuGameBase osu)
+ private void assertCorrectMetadata(Live