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/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/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/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/osu.Android.props b/osu.Android.props
index b296c114e9..4e5b9fdbb1 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
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.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs
index bb22fab51c..bf9467700c 100644
--- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs
+++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Benchmarks
public class BenchmarkRealmReads : BenchmarkTest
{
private TemporaryNativeStorage storage;
- private RealmContextFactory realmFactory;
+ private RealmAccess realm;
private UpdateThread updateThread;
[Params(1, 100, 1000)]
@@ -27,9 +27,9 @@ namespace osu.Game.Benchmarks
storage = new TemporaryNativeStorage("realm-benchmark");
storage.DeleteDirectory(string.Empty);
- realmFactory = new RealmContextFactory(storage, "client");
+ realm = new RealmAccess(storage, "client");
- realmFactory.Run(realm =>
+ realm.Run(r =>
{
realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo })));
});
@@ -41,9 +41,9 @@ namespace osu.Game.Benchmarks
[Benchmark]
public void BenchmarkDirectPropertyRead()
{
- realmFactory.Run(realm =>
+ realm.Run(r =>
{
- var beatmapSet = realm.All().First();
+ var beatmapSet = r.All().First();
for (int i = 0; i < ReadsPerFetch; i++)
{
@@ -61,7 +61,7 @@ namespace osu.Game.Benchmarks
{
try
{
- var beatmapSet = realmFactory.Context.All().First();
+ var beatmapSet = realm.Realm.All().First();
for (int i = 0; i < ReadsPerFetch; i++)
{
@@ -80,9 +80,9 @@ namespace osu.Game.Benchmarks
[Benchmark]
public void BenchmarkRealmLivePropertyRead()
{
- realmFactory.Run(realm =>
+ realm.Run(r =>
{
- var beatmapSet = realm.All().First().ToLive(realmFactory);
+ var beatmapSet = r.All().First().ToLive(realm);
for (int i = 0; i < ReadsPerFetch; i++)
{
@@ -100,7 +100,7 @@ namespace osu.Game.Benchmarks
{
try
{
- var beatmapSet = realmFactory.Context.All().First().ToLive(realmFactory);
+ var beatmapSet = realm.Realm.All().First().ToLive(realm);
for (int i = 0; i < ReadsPerFetch; i++)
{
@@ -119,9 +119,9 @@ namespace osu.Game.Benchmarks
[Benchmark]
public void BenchmarkDetachedPropertyRead()
{
- realmFactory.Run(realm =>
+ realm.Run(r =>
{
- var beatmapSet = realm.All().First().Detach();
+ var beatmapSet = r.All().First().Detach();
for (int i = 0; i < ReadsPerFetch; i++)
{
@@ -133,7 +133,7 @@ namespace osu.Game.Benchmarks
[GlobalCleanup]
public void Cleanup()
{
- realmFactory?.Dispose();
+ realm?.Dispose();
storage?.Dispose();
updateThread?.Exit();
}
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/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.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/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.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/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.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 6ec14e6351..0459753b28 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -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);
diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
index 7aa2dc7093..9e440c6bce 100644
--- a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
+++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs
@@ -53,9 +53,9 @@ namespace osu.Game.Tests.Beatmaps.IO
private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
{
- var realmContextFactory = osu.Dependencies.Get();
+ var realm = osu.Dependencies.Get();
- realmContextFactory.Run(realm => BeatmapImporterTests.EnsureLoaded(realm, timeout));
+ realm.Run(r => BeatmapImporterTests.EnsureLoaded(r, timeout));
// TODO: add back some extra checks outside of the realm ones?
// var set = queryBeatmapSets().First();
diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
index 53e4ef07e7..5cbede54f5 100644
--- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
+++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
@@ -155,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
{
diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs
index 227314cffd..69dd2d930a 100644
--- a/osu.Game.Tests/Database/BeatmapImporterTests.cs
+++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs
@@ -38,10 +38,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestDetachBeatmapSet()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using (var importer = new BeatmapModelManager(realmFactory, storage))
- using (new RulesetStore(realmFactory, storage))
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
{
ILive? beatmapSet;
@@ -82,10 +82,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestUpdateDetachedBeatmapSet()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using (var importer = new BeatmapModelManager(realmFactory, storage))
- using (new RulesetStore(realmFactory, storage))
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
{
ILive? beatmapSet;
@@ -139,53 +139,53 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportBeatmapThenCleanup()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using (var importer = new BeatmapModelManager(realmFactory, storage))
- using (new RulesetStore(realmFactory, storage))
+ using (var importer = new BeatmapModelManager(realm, storage))
+ using (new RulesetStore(realm, storage))
{
ILive? 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 BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(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 BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(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;
@@ -198,24 +198,24 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDelete()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(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 BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? tempPath = TestResources.GetTestBeatmapForImport();
@@ -224,7 +224,7 @@ namespace osu.Game.Tests.Database
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);
@@ -233,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 BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(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 BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -274,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);
@@ -292,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);
@@ -311,10 +311,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenImportWithChangedHashedFile()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -323,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);
@@ -343,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);
@@ -363,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 BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -375,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);
@@ -392,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);
@@ -411,10 +411,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenImportWithDifferentFilename()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -423,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);
@@ -440,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);
@@ -460,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 BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(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();
@@ -476,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");
@@ -485,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 BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
var progressNotification = new ImportProgressNotification();
@@ -510,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);
@@ -521,7 +521,7 @@ namespace osu.Game.Tests.Database
[Test]
public void TestRollbackOnFailure()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
int loggedExceptionCount = 0;
@@ -531,16 +531,16 @@ namespace osu.Game.Tests.Database
Interlocked.Increment(ref loggedExceptionCount);
};
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(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();
@@ -565,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);
@@ -579,18 +579,18 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDeleteThenImportOptimisedPath()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(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);
@@ -601,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 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);
@@ -627,22 +659,22 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportThenDeleteThenImportWithOnlineIDsMissing()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(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);
@@ -653,10 +685,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithDuplicateBeatmapIDs()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
var metadata = new BeatmapMetadata
{
@@ -667,7 +699,7 @@ namespace osu.Game.Tests.Database
}
};
- var ruleset = realmFactory.Context.All().First();
+ var ruleset = realm.Realm.All().First();
var toImport = new BeatmapSetInfo
{
@@ -686,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);
@@ -699,15 +731,15 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWhenFileOpen()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(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");
});
@@ -716,10 +748,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithDuplicateHashes()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -740,7 +772,7 @@ namespace osu.Game.Tests.Database
await importer.Import(temp);
- EnsureLoaded(realmFactory.Context);
+ EnsureLoaded(realm.Realm);
}
finally
{
@@ -752,10 +784,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportNestedStructure()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -780,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");
}
@@ -794,10 +826,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestImportWithIgnoredDirectoryInArchive()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(realmFactory, storage);
+ using var importer = new BeatmapModelManager(realm, storage);
+ using var store = new RulesetStore(realm, storage);
string? temp = TestResources.GetTestBeatmapForImport();
@@ -830,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");
@@ -845,22 +877,22 @@ namespace osu.Game.Tests.Database
[Test]
public void TestUpdateBeatmapInfo()
{
- RunTestWithRealmAsync(async (realmFactory, storage) =>
+ RunTestWithRealmAsync(async (realm, storage) =>
{
- using var importer = new BeatmapModelManager(realmFactory, storage);
- using var store = new RulesetStore(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
- BeatmapSetInfo 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");
- BeatmapInfo 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"));
});
}
@@ -1004,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 9ebe94b383..dc0d42595b 100644
--- a/osu.Game.Tests/Database/GeneralUsageTests.cs
+++ b/osu.Game.Tests/Database/GeneralUsageTests.cs
@@ -21,15 +21,15 @@ namespace osu.Game.Tests.Database
[Test]
public void TestConstructRealm()
{
- RunTestWithRealm((realmFactory, _) => { realmFactory.Run(realm => realm.Refresh()); });
+ RunTestWithRealm((realm, _) => { realm.Run(r => r.Refresh()); });
}
[Test]
public void TestBlockOperations()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- using (realmFactory.BlockAllOperations())
+ using (realm.BlockAllOperations())
{
}
});
@@ -42,24 +42,25 @@ namespace osu.Game.Tests.Database
[Test]
public void TestNestedContextCreationWithSubscription()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
bool callbackRan = false;
- realmFactory.Run(realm =>
+ realm.RegisterCustomSubscription(r =>
{
- var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) =>
+ var subscription = r.All().QueryAsyncWithNotifications((sender, changes, error) =>
{
- realmFactory.Run(_ =>
+ realm.Run(_ =>
{
callbackRan = true;
});
});
// Force the callback above to run.
- realmFactory.Run(r => r.Refresh());
+ realm.Run(rr => rr.Refresh());
subscription?.Dispose();
+ return null;
});
Assert.IsTrue(callbackRan);
@@ -69,14 +70,14 @@ namespace osu.Game.Tests.Database
[Test]
public void TestBlockOperationsWithContention()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
ManualResetEventSlim stopThreadedUsage = new ManualResetEventSlim();
ManualResetEventSlim hasThreadedUsage = new ManualResetEventSlim();
Task.Factory.StartNew(() =>
{
- realmFactory.Run(_ =>
+ realm.Run(_ =>
{
hasThreadedUsage.Set();
@@ -86,15 +87,30 @@ namespace osu.Game.Tests.Database
hasThreadedUsage.Wait();
+ // Usually the host would run the synchronization context work per frame.
+ // For the sake of keeping this test simple (there's only one update invocation),
+ // let's replace it so we can ensure work is run immediately.
+ SynchronizationContext.SetSynchronizationContext(new ImmediateExecuteSynchronizationContext());
+
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())
+ {
+ }
});
}
+
+ private class ImmediateExecuteSynchronizationContext : SynchronizationContext
+ {
+ public override void Post(SendOrPostCallback d, object? state) => d(state);
+ }
}
}
diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs
index 7b1cf763d6..2e3f708f79 100644
--- a/osu.Game.Tests/Database/RealmLiveTests.cs
+++ b/osu.Game.Tests/Database/RealmLiveTests.cs
@@ -21,11 +21,11 @@ namespace osu.Game.Tests.Database
[Test]
public void TestLiveEquality()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
- ILive beatmap = realmFactory.Run(realm => realm.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory));
+ ILive beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm));
- ILive beatmap2 = realmFactory.Run(realm => realm.All().First().ToLive(realmFactory));
+ ILive beatmap2 = realm.Run(r => r.All().First().ToLive(realm));
Assert.AreEqual(beatmap, beatmap2);
});
@@ -34,20 +34,20 @@ namespace osu.Game.Tests.Database
[Test]
public void TestAccessAfterStorageMigrate()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
ILive? liveBeatmap = null;
- realmFactory.Run(realm =>
+ realm.Run(r =>
{
- realm.Write(r => r.Add(beatmap));
+ r.Write(_ => r.Add(beatmap));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
- using (realmFactory.BlockAllOperations())
+ using (realm.BlockAllOperations())
{
// recycle realm before migrating
}
@@ -66,13 +66,13 @@ namespace osu.Game.Tests.Database
[Test]
public void TestAccessAfterAttach()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
- var liveBeatmap = beatmap.ToLive(realmFactory);
+ var liveBeatmap = beatmap.ToLive(realm);
- realmFactory.Run(realm => realm.Write(r => r.Add(beatmap)));
+ realm.Run(r => r.Write(_ => r.Add(beatmap)));
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
});
@@ -98,16 +98,16 @@ namespace osu.Game.Tests.Database
[Test]
public void TestScopedReadWithoutContext()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
ILive? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- realmFactory.Run(threadContext =>
+ realm.Run(threadContext =>
{
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
@@ -127,16 +127,16 @@ namespace osu.Game.Tests.Database
[Test]
public void TestScopedWriteWithoutContext()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
ILive? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- realmFactory.Run(threadContext =>
+ realm.Run(threadContext =>
{
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
@@ -153,10 +153,10 @@ namespace osu.Game.Tests.Database
[Test]
public void TestValueAccessNonManaged()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
- var liveBeatmap = beatmap.ToLive(realmFactory);
+ var liveBeatmap = beatmap.ToLive(realm);
Assert.DoesNotThrow(() =>
{
@@ -168,17 +168,17 @@ namespace osu.Game.Tests.Database
[Test]
public void TestValueAccessWithOpenContextFails()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
ILive? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- realmFactory.Run(threadContext =>
+ realm.Run(threadContext =>
{
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
@@ -193,7 +193,7 @@ namespace osu.Game.Tests.Database
});
// Can't be used, even from within a valid context.
- realmFactory.Run(threadContext =>
+ realm.Run(threadContext =>
{
Assert.Throws(() =>
{
@@ -207,16 +207,16 @@ namespace osu.Game.Tests.Database
[Test]
public void TestValueAccessWithoutOpenContextFails()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
ILive? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- realmFactory.Run(threadContext =>
+ realm.Run(threadContext =>
{
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
@@ -235,18 +235,18 @@ namespace osu.Game.Tests.Database
[Test]
public void TestLiveAssumptions()
{
- RunTestWithRealm((realmFactory, _) =>
+ RunTestWithRealm((realm, _) =>
{
int changesTriggered = 0;
- realmFactory.Run(outerRealm =>
+ realm.RegisterCustomSubscription(outerRealm =>
{
outerRealm.All().QueryAsyncWithNotifications(gotChange);
ILive? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
- realmFactory.Run(innerRealm =>
+ realm.Run(innerRealm =>
{
var ruleset = CreateRuleset();
var beatmap = innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
@@ -255,7 +255,7 @@ namespace osu.Game.Tests.Database
// not just a refresh from the resolved Live.
innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
- liveBeatmap = beatmap.ToLive(realmFactory);
+ liveBeatmap = beatmap.ToLive(realm);
});
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
@@ -282,6 +282,8 @@ namespace osu.Game.Tests.Database
r.Remove(resolved);
});
});
+
+ return null;
});
void gotChange(IRealmCollection sender, ChangeSet changes, Exception error)
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 0cee165f75..c2339dd9ad 100644
--- a/osu.Game.Tests/Database/RealmTest.cs
+++ b/osu.Game.Tests/Database/RealmTest.cs
@@ -30,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))
{
@@ -39,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))
{
@@ -62,15 +62,15 @@ 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();
}
}));
}
@@ -138,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 4416da6f92..7544142b70 100644
--- a/osu.Game.Tests/Database/RulesetStoreTests.cs
+++ b/osu.Game.Tests/Database/RulesetStoreTests.cs
@@ -12,37 +12,37 @@ namespace osu.Game.Tests.Database
[Test]
public void TestCreateStore()
{
- RunTestWithRealm((realmFactory, storage) =>
+ RunTestWithRealm((realm, storage) =>
{
- var rulesets = new RulesetStore(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 RulesetStore(realmFactory, storage);
- var rulesets2 = new RulesetStore(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 RulesetStore(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 c1041e9fd6..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,11 +60,11 @@ namespace osu.Game.Tests.Database
KeyBindingContainer testContainer = new TestKeyBindingContainer();
// Add some excess bindings for an action which only supports 1.
- realmContextFactory.Write(realm =>
+ realm.Write(r =>
{
- realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A)));
- realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S)));
- realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D)));
+ 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));
@@ -76,9 +76,9 @@ namespace osu.Game.Tests.Database
private int queryCount(GlobalAction? match = null)
{
- return realmContextFactory.Run(realm =>
+ 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();
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Database
keyBindingStore.Register(testContainer, Enumerable.Empty());
- realmContextFactory.Run(outerRealm =>
+ realm.Run(outerRealm =>
{
var backBinding = outerRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back);
@@ -100,7 +100,7 @@ namespace osu.Game.Tests.Database
var tsr = ThreadSafeReference.Create(backBinding);
- realmContextFactory.Run(innerRealm =>
+ realm.Run(innerRealm =>
{
var binding = innerRealm.ResolveReference(tsr);
innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace));
@@ -117,7 +117,7 @@ namespace osu.Game.Tests.Database
[TearDown]
public void TearDown()
{
- realmContextFactory.Dispose();
+ realm.Dispose();
storage.DeleteDirectory(string.Empty);
}
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/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/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
index 61ef31e07e..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}"));
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index 1d639c6418..95c15367aa 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,8 +60,8 @@ namespace osu.Game.Tests.Online
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
testBeatmapSet = testBeatmapInfo.BeatmapSet;
- ContextFactory.Write(r => r.RemoveAll());
- ContextFactory.Write(r => r.RemoveAll());
+ Realm.Write(r => r.RemoveAll());
+ Realm.Write(r => r.RemoveAll());
selectedItem.Value = new PlaylistItem
{
@@ -91,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);
}
@@ -164,32 +164,32 @@ namespace osu.Game.Tests.Online
{
public TaskCompletionSource AllowImport = new TaskCompletionSource();
- public Task> CurrentImportTask { get; private set; }
+ public ILive CurrentImport { get; private set; }
- public TestBeatmapManager(Storage storage, RealmContextFactory 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, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
+ protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
{
- return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, onlineLookupQueue);
+ return new TestBeatmapModelManager(this, storage, realm, rulesets, onlineLookupQueue);
}
internal class TestBeatmapModelManager : BeatmapModelManager
{
private readonly TestBeatmapManager testBeatmapManager;
- public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
- : base(databaseContextFactory, storage, beatmapOnlineLookupQueue)
+ 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 ILive 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 d2cab09ac9..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);
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index dd12c94855..8de9f0a292 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -5,7 +5,6 @@ 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;
@@ -25,7 +24,7 @@ namespace osu.Game.Tests.Scores.IO
public class ImportScoreTest : ImportTest
{
[Test]
- public async Task TestBasicImport()
+ public void TestBasicImport()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -49,7 +48,7 @@ namespace osu.Game.Tests.Scores.IO
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);
@@ -67,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public async Task TestImportMods()
+ public void TestImportMods()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -85,7 +84,7 @@ namespace osu.Game.Tests.Scores.IO
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));
@@ -98,7 +97,7 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public async Task TestImportStatistics()
+ public void TestImportStatistics()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -120,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]);
@@ -133,7 +132,7 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public async Task TestOnlineScoreIsAvailableLocally()
+ public void TestOnlineScoreIsAvailableLocally()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
@@ -143,7 +142,7 @@ namespace osu.Game.Tests.Scores.IO
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
- await LoadScoreIntoOsu(osu, new ScoreInfo
+ LoadScoreIntoOsu(osu, new ScoreInfo
{
User = new APIUser { Username = "Test user" },
BeatmapInfo = beatmap.Beatmaps.First(),
@@ -168,13 +167,14 @@ 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)
{
// clone to avoid attaching the input score to realm.
score = score.DeepClone();
var scoreManager = osu.Dependencies.Get();
- await scoreManager.Import(score, archive);
+
+ scoreManager.Import(score, archive);
return scoreManager.Query(_ => true);
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index 4ab4c08353..40e7c0a844 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -47,10 +47,10 @@ namespace osu.Game.Tests.Visual.Background
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(Realm);
manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
index 18572ac211..d4c13059da 100644
--- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
+++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
@@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.Collections
[BackgroundDependencyLoader]
private void load(GameHost host)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
index 243bb71e26..cf6488f721 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
@@ -13,6 +13,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit;
+using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps.IO;
namespace osu.Game.Tests.Visual.Editing
@@ -37,11 +38,8 @@ namespace osu.Game.Tests.Visual.Editing
base.SetUpSteps();
}
- protected override void LoadEditor()
- {
- Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First());
- base.LoadEditor();
- }
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First());
[Test]
public void TestBasicSwitch()
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
index 2386446e96..e3fb44534b 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
@@ -13,6 +13,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Setup;
+using osu.Game.Storyboards;
using osu.Game.Tests.Resources;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
@@ -39,11 +40,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString());
}
- protected override void LoadEditor()
- {
- Beatmap.Value = new DummyWorkingBeatmap(Audio, null);
- base.LoadEditor();
- }
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null);
[Test]
public void TestCreateNewBeatmap()
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
index bf3b46c6f7..96f815621c 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
@@ -3,6 +3,7 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Input;
using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints;
@@ -40,6 +41,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
+ AddStep("Set beat divisor", () => editor.Dependencies.Get().Value = 16);
AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7);
AddStep("Set artist and title", () =>
{
@@ -88,6 +90,7 @@ namespace osu.Game.Tests.Visual.Editing
private void checkMutations()
{
AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
+ AddAssert("Beatmap has correct beat divisor", () => editorBeatmap.BeatmapInfo.BeatDivisor == 16);
AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7);
AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title");
AddAssert("Beatmap has correct author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username == "author");
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
index bb630e5d5c..79afc8cf27 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs
@@ -17,6 +17,7 @@ using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Game.Screens.Edit.GameplayTest;
using osu.Game.Screens.Play;
+using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps.IO;
using osuTK.Graphics;
using osuTK.Input;
@@ -43,9 +44,11 @@ namespace osu.Game.Tests.Visual.Editing
base.SetUpSteps();
}
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0));
+
protected override void LoadEditor()
{
- Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0));
SelectedMods.Value = new[] { new ModCinema() };
base.LoadEditor();
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs
new file mode 100644
index 0000000000..b34974dfc7
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs
@@ -0,0 +1,88 @@
+// 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.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Screens.Edit.Timing;
+
+namespace osu.Game.Tests.Visual.Editing
+{
+ public class TestSceneLabelledTimeSignature : OsuManualInputManagerTestScene
+ {
+ private LabelledTimeSignature timeSignature;
+
+ private void createLabelledTimeSignature(TimeSignature initial) => AddStep("create labelled time signature", () =>
+ {
+ Child = timeSignature = new LabelledTimeSignature
+ {
+ Label = "Time Signature",
+ RelativeSizeAxes = Axes.None,
+ Width = 400,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = { Value = initial }
+ };
+ });
+
+ private OsuTextBox numeratorTextBox => timeSignature.ChildrenOfType().Single();
+
+ [Test]
+ public void TestInitialValue()
+ {
+ createLabelledTimeSignature(TimeSignature.SimpleTriple);
+ AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple));
+ }
+
+ [Test]
+ public void TestChangeViaCurrent()
+ {
+ createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+
+ AddStep("set current to 5/4", () => timeSignature.Current.Value = new TimeSignature(5));
+
+ AddAssert("current is 5/4", () => timeSignature.Current.Value.Equals(new TimeSignature(5)));
+ AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "5");
+
+ AddStep("set current to 3/4", () => timeSignature.Current.Value = TimeSignature.SimpleTriple);
+
+ AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple));
+ AddAssert("numerator is 3", () => numeratorTextBox.Current.Value == "3");
+ }
+
+ [Test]
+ public void TestChangeNumerator()
+ {
+ createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+
+ AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox));
+
+ AddStep("set numerator to 7", () => numeratorTextBox.Current.Value = "7");
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+
+ AddStep("drop focus", () => InputManager.ChangeFocus(null));
+ AddAssert("current is 7/4", () => timeSignature.Current.Value.Equals(new TimeSignature(7)));
+ }
+
+ [Test]
+ public void TestInvalidChangeRollbackOnCommit()
+ {
+ createLabelledTimeSignature(TimeSignature.SimpleQuadruple);
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+
+ AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox));
+
+ AddStep("set numerator to 0", () => numeratorTextBox.Current.Value = "0");
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+
+ AddStep("drop focus", () => InputManager.ChangeFocus(null));
+ AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple));
+ AddAssert("numerator is 4", () => numeratorTextBox.Current.Value == "4");
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs
index 951ee1489d..759e4fa4ec 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs
@@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual.Gameplay
Add(new ModNightcore.NightcoreBeatContainer());
- AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleQuadruple));
- AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleTriple));
+ AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleQuadruple));
+ AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleTriple));
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
index 8199389b36..a12171401a 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
@@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
- AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).GetResultSafely());
+ AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)));
AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable);
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs
index 4754a73f83..642cc68de5 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs
@@ -8,6 +8,8 @@ using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Overlays.Login;
+using osu.Game.Users.Drawables;
+using osuTK.Input;
namespace osu.Game.Tests.Visual.Menus
{
@@ -15,6 +17,7 @@ namespace osu.Game.Tests.Visual.Menus
public class TestSceneLoginPanel : OsuManualInputManagerTestScene
{
private LoginPanel loginPanel;
+ private int hideCount;
[SetUpSteps]
public void SetUpSteps()
@@ -26,6 +29,7 @@ namespace osu.Game.Tests.Visual.Menus
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.5f,
+ RequestHide = () => hideCount++,
});
});
}
@@ -51,5 +55,22 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password");
AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick());
}
+
+ [Test]
+ public void TestClickingOnFlagClosesPanel()
+ {
+ AddStep("reset hide count", () => hideCount = 0);
+
+ AddStep("logout", () => API.Logout());
+ AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password");
+ AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick());
+
+ AddStep("click on flag", () =>
+ {
+ InputManager.MoveMouseTo(loginPanel.ChildrenOfType().First());
+ InputManager.Click(MouseButton.Left);
+ });
+ AddAssert("hide requested", () => hideCount == 1);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
index 3ebc64cd0b..10a82089b3 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Menus
Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null;
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
- AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).WaitSafely(), 5);
+ AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5);
AddStep("import beatmap with track", () =>
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
index d4282ff21e..4cd19b53a4 100644
--- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
@@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
}
public override void SetUpSteps()
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
index 99c867b014..92accb0cd1 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
@@ -8,7 +8,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
-using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
@@ -43,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
}
[Test]
@@ -158,7 +157,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Debug.Assert(beatmap.BeatmapSet != null);
- AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet).GetResultSafely());
+ AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet));
createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach()));
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 373b165acc..3f151a0ae8 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -61,9 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
}
public override void SetUpSteps()
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
index 15ebe0ee00..5465061891 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
@@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
beatmaps = new List();
@@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
beatmapSetInfo.Beatmaps.Add(beatmap);
}
- manager.Import(beatmapSetInfo).WaitSafely();
+ manager.Import(beatmapSetInfo);
}
public override void SetUpSteps()
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index 012a2fd960..9d14d80d07 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
index d547b42891..d970ab6c34 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
@@ -33,9 +33,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
}
[SetUp]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
index 965b142ed7..d83421ee3a 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
@@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
}
public override void SetUpSteps()
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
index 1c346e09d5..9867e5225e 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
@@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
index 221732910b..42ae279667 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
@@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
index 0b0006e437..d933491ab6 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
@@ -6,7 +6,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
-using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Utils;
@@ -34,13 +33,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
var beatmapSet = TestResources.CreateTestBeatmapSetInfo();
- manager.Import(beatmapSet).WaitSafely();
+ manager.Import(beatmapSet);
}
public override void SetUpSteps()
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
index 39cde0ad87..73c67d26d9 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
@@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
}
public override void SetUpSteps()
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs
index 0f314242b4..bfcefdbbfe 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs
@@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Navigation
.ChildrenOfType().SingleOrDefault();
private RealmKeyBinding firstOsuRulesetKeyBindings => Game.Dependencies
- .Get().Context
+ .Get().Realm
.All()
.AsEnumerable()
.First(k => k.RulesetName == "osu" && k.ActionInt == 0);
diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs
index f6c53e76c4..63226de750 100644
--- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs
@@ -4,7 +4,6 @@
using System;
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Extensions;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
@@ -125,7 +124,7 @@ namespace osu.Game.Tests.Visual.Navigation
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
},
}
- }).GetResultSafely()?.Value;
+ })?.Value;
});
AddAssert($"import {i} succeeded", () => imported != null);
diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
index 7bd8110374..7656bf79dc 100644
--- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
@@ -4,7 +4,6 @@
using System;
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Extensions;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
@@ -60,7 +59,7 @@ namespace osu.Game.Tests.Visual.Navigation
Ruleset = new OsuRuleset().RulesetInfo
},
}
- }).GetResultSafely()?.Value;
+ })?.Value;
});
}
@@ -135,7 +134,7 @@ namespace osu.Game.Tests.Visual.Navigation
BeatmapInfo = beatmap.Beatmaps.First(),
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo,
User = new GuestUser(),
- }).GetResultSafely().Value;
+ }).Value;
});
AddAssert($"import {i} succeeded", () => imported != null);
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs
index bc9f759bdd..68225f6d64 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs
@@ -8,7 +8,6 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
-using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
@@ -40,9 +39,9 @@ namespace osu.Game.Tests.Visual.Playlists
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
}
[SetUpSteps]
@@ -151,7 +150,7 @@ namespace osu.Game.Tests.Visual.Playlists
Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null);
- manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).WaitSafely();
+ manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet);
});
// Create the room using the real beatmap values.
@@ -196,7 +195,7 @@ namespace osu.Game.Tests.Visual.Playlists
Debug.Assert(originalBeatmap.BeatmapInfo.BeatmapSet != null);
- manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).WaitSafely();
+ manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet);
});
AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash);
@@ -219,7 +218,7 @@ namespace osu.Game.Tests.Visual.Playlists
Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null);
- importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet).GetResultSafely()?.Value.Detach();
+ importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)?.Value.Detach();
});
private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
index a77480ee54..988f429ff5 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
@@ -36,17 +36,17 @@ namespace osu.Game.Tests.Visual.Ranking
private BeatmapManager beatmaps { get; set; }
[Resolved]
- private RealmContextFactory realmContextFactory { get; set; }
+ private RealmAccess realm { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
- realmContextFactory.Run(realm =>
+ realm.Run(r =>
{
- var beatmapInfo = realm.All()
- .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0)
- .FirstOrDefault();
+ var beatmapInfo = r.All()
+ .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0)
+ .FirstOrDefault();
if (beatmapInfo != null)
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
index 2e1a66be5f..e31be1d51a 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
@@ -42,10 +42,10 @@ namespace osu.Game.Tests.Visual.SongSelect
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
- dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default));
- dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler));
- Dependencies.Cache(ContextFactory);
+ dependencies.Cache(rulesetStore = new RulesetStore(Realm));
+ dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default));
+ dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler));
+ Dependencies.Cache(Realm);
return dependencies;
}
@@ -180,7 +180,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep(@"Load new scores via manager", () =>
{
foreach (var score in generateSampleScores(beatmapInfo()))
- scoreManager.Import(score).WaitSafely();
+ scoreManager.Import(score);
});
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
index b7bc0c37e1..940d001c5b 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
@@ -184,7 +183,7 @@ namespace osu.Game.Tests.Visual.SongSelect
beatmap.DifficultyName = $"SR{i + 1}";
}
- return Game.BeatmapManager.Import(beatmapSet).GetResultSafely()?.Value;
+ return Game.BeatmapManager.Import(beatmapSet)?.Value;
}
private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null);
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs
index ca8e9d2eff..b384061531 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs
@@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.SongSelect
[BackgroundDependencyLoader]
private void load(GameHost host)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(Realm);
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 6295a52bdd..630171a4d0 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -8,7 +8,6 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
-using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
@@ -47,9 +46,9 @@ namespace osu.Game.Tests.Visual.SongSelect
{
// These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install.
// At a point we have isolated interactive test runs enough, this can likely be removed.
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(ContextFactory);
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default));
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(Realm);
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default));
Dependencies.Cache(music = new MusicController());
@@ -260,7 +259,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("import multi-ruleset map", () =>
{
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray();
- manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)).WaitSafely();
+ manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets));
});
}
else
@@ -675,7 +674,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("import multi-ruleset map", () =>
{
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray();
- manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely();
+ manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets));
});
int previousSetID = 0;
@@ -715,7 +714,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("import multi-ruleset map", () =>
{
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray();
- manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely();
+ manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets));
});
DrawableCarouselBeatmapSet set = null;
@@ -764,7 +763,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("import huge difficulty count map", () =>
{
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray();
- imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).GetResultSafely()?.Value;
+ imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets))?.Value;
});
AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First()));
@@ -873,7 +872,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id));
- private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())).WaitSafely();
+ private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray()));
private void checkMusicPlaying(bool playing) =>
AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing);
@@ -903,7 +902,7 @@ namespace osu.Game.Tests.Visual.SongSelect
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray();
for (int i = 0; i < 10; i++)
- manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)).WaitSafely();
+ manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets));
});
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs
index 3aa5a759e6..8e5f76a2eb 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs
@@ -28,10 +28,10 @@ namespace osu.Game.Tests.Visual.SongSelect
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
- Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, ContextFactory, Scheduler));
- Dependencies.Cache(ContextFactory);
+ Dependencies.Cache(rulesets = new RulesetStore(Realm));
+ Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
+ Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler));
+ Dependencies.Cache(Realm);
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
index f43354514b..4826d2fb33 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private BeatmapInfo beatmapInfo;
[Resolved]
- private RealmContextFactory realmFactory { get; set; }
+ private RealmAccess realm { get; set; }
[Cached]
private readonly DialogOverlay dialogOverlay;
@@ -87,10 +87,10 @@ namespace osu.Game.Tests.Visual.UserInterface
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
- dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default));
- dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, ContextFactory, Scheduler));
- Dependencies.Cache(ContextFactory);
+ dependencies.Cache(rulesetStore = new RulesetStore(Realm));
+ dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default));
+ dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler));
+ Dependencies.Cache(Realm);
var imported = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely();
@@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Ruleset = new OsuRuleset().RulesetInfo,
};
- importedScores.Add(scoreManager.Import(score).GetResultSafely().Value);
+ importedScores.Add(scoreManager.Import(score).Value);
}
});
@@ -122,10 +122,10 @@ namespace osu.Game.Tests.Visual.UserInterface
[SetUp]
public void Setup() => Schedule(() =>
{
- realmFactory.Run(realm =>
+ realm.Run(r =>
{
// Due to soft deletions, we can re-use deleted scores between test runs
- scoreManager.Undelete(realm.All().Where(s => s.DeletePending).ToList());
+ scoreManager.Undelete(r.All().Where(s => s.DeletePending).ToList());
});
leaderboard.Scores = null;
diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
index fc5d3b652f..26fb03bed4 100644
--- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
+++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
[Test]
public void TestCustomDirectory()
{
- using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestCustomDirectory))) // don't use clean run as we are writing a config file.
+ using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestCustomDirectory), null)) // don't use clean run as we are writing a config file.
{
string osuDesktopStorage = Path.Combine(host.UserStoragePaths.First(), nameof(TestCustomDirectory));
const string custom_tournament = "custom";
@@ -68,7 +68,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
[Test]
public void TestMigration()
{
- using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestMigration))) // don't use clean run as we are writing test files for migration.
+ using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestMigration), null)) // don't use clean run as we are writing test files for migration.
{
string osuRoot = Path.Combine(host.UserStoragePaths.First(), nameof(TestMigration));
string configFile = Path.Combine(osuRoot, "tournament.ini");
diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
index 03252e3be6..80cc9be5c1 100644
--- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
+++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
public void CheckIPCLocation()
{
// don't use clean run because files are being written before osu! launches.
- using (var host = new TestRunHeadlessGameHost(nameof(CheckIPCLocation)))
+ using (var host = new TestRunHeadlessGameHost(nameof(CheckIPCLocation), null))
{
string basePath = Path.Combine(host.UserStoragePaths.First(), nameof(CheckIPCLocation));
diff --git a/osu.Game.Tournament.Tests/TournamentTestRunner.cs b/osu.Game.Tournament.Tests/TournamentTestRunner.cs
index 1f63f7c545..229ab41a1e 100644
--- a/osu.Game.Tournament.Tests/TournamentTestRunner.cs
+++ b/osu.Game.Tournament.Tests/TournamentTestRunner.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Tournament.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 TournamentTestBrowser());
return 0;
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 43e4b482bd..a1c1982f00 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -10,7 +10,6 @@ using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
-using osu.Framework.Extensions;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Framework.Testing;
@@ -41,11 +40,11 @@ namespace osu.Game.Beatmaps
private readonly WorkingBeatmapCache workingBeatmapCache;
private readonly BeatmapOnlineLookupQueue? onlineBeatmapLookupQueue;
- private readonly RealmContextFactory contextFactory;
+ private readonly RealmAccess realm;
- public BeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false)
+ public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false)
{
- this.contextFactory = contextFactory;
+ this.realm = realm;
if (performOnlineLookups)
{
@@ -55,11 +54,11 @@ namespace osu.Game.Beatmaps
onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
}
- var userResources = new RealmFileStore(contextFactory, storage).Store;
+ var userResources = new RealmFileStore(realm, storage).Store;
BeatmapTrackStore = audioManager.GetTrackStore(userResources);
- beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, onlineBeatmapLookupQueue);
+ beatmapModelManager = CreateBeatmapModelManager(storage, realm, rulesets, onlineBeatmapLookupQueue);
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host);
beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache;
@@ -70,8 +69,8 @@ namespace osu.Game.Beatmaps
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
}
- protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) =>
- new BeatmapModelManager(contextFactory, storage, onlineLookupQueue);
+ protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) =>
+ new BeatmapModelManager(realm, storage, onlineLookupQueue);
///
/// Create a new .
@@ -105,7 +104,7 @@ namespace osu.Game.Beatmaps
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
b.BeatmapSet = beatmapSet;
- var imported = beatmapModelManager.Import(beatmapSet).GetResultSafely();
+ var imported = beatmapModelManager.Import(beatmapSet);
if (imported == null)
throw new InvalidOperationException("Failed to import new beatmap");
@@ -119,12 +118,12 @@ namespace osu.Game.Beatmaps
/// The beatmap difficulty to hide.
public void Hide(BeatmapInfo beatmapInfo)
{
- contextFactory.Run(realm =>
+ realm.Run(r =>
{
- using (var transaction = realm.BeginWrite())
+ using (var transaction = r.BeginWrite())
{
if (!beatmapInfo.IsManaged)
- beatmapInfo = realm.Find(beatmapInfo.ID);
+ beatmapInfo = r.Find(beatmapInfo.ID);
beatmapInfo.Hidden = true;
transaction.Commit();
@@ -138,12 +137,12 @@ namespace osu.Game.Beatmaps
/// The beatmap difficulty to restore.
public void Restore(BeatmapInfo beatmapInfo)
{
- contextFactory.Run(realm =>
+ realm.Run(r =>
{
- using (var transaction = realm.BeginWrite())
+ using (var transaction = r.BeginWrite())
{
if (!beatmapInfo.IsManaged)
- beatmapInfo = realm.Find(beatmapInfo.ID);
+ beatmapInfo = r.Find(beatmapInfo.ID);
beatmapInfo.Hidden = false;
transaction.Commit();
@@ -153,11 +152,11 @@ namespace osu.Game.Beatmaps
public void RestoreAll()
{
- contextFactory.Run(realm =>
+ realm.Run(r =>
{
- using (var transaction = realm.BeginWrite())
+ using (var transaction = r.BeginWrite())
{
- foreach (var beatmap in realm.All().Where(b => b.Hidden))
+ foreach (var beatmap in r.All().Where(b => b.Hidden))
beatmap.Hidden = false;
transaction.Commit();
@@ -171,10 +170,10 @@ namespace osu.Game.Beatmaps
/// A list of available .
public List GetAllUsableBeatmapSets()
{
- return contextFactory.Run(realm =>
+ return realm.Run(r =>
{
- realm.Refresh();
- return realm.All().Where(b => !b.DeletePending).Detach();
+ r.Refresh();
+ return r.All().Where(b => !b.DeletePending).Detach();
});
}
@@ -185,7 +184,7 @@ namespace osu.Game.Beatmaps
/// The first result for the provided query, or null if no results were found.
public ILive? QueryBeatmapSet(Expression> query)
{
- return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.ToLive(contextFactory));
+ return realm.Run(r => r.All().FirstOrDefault(query)?.ToLive(realm));
}
#region Delegation to BeatmapModelManager (methods which previously existed locally).
@@ -240,9 +239,9 @@ namespace osu.Game.Beatmaps
public void Delete(Expression>? filter = null, bool silent = false)
{
- contextFactory.Run(realm =>
+ realm.Run(r =>
{
- var items = realm.All().Where(s => !s.DeletePending && !s.Protected);
+ var items = r.All().Where(s => !s.DeletePending && !s.Protected);
if (filter != null)
items = items.Where(filter);
@@ -253,7 +252,7 @@ namespace osu.Game.Beatmaps
public void UndeleteAll()
{
- contextFactory.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList()));
+ realm.Run(r => beatmapModelManager.Undelete(r.All().Where(s => s.DeletePending).ToList()));
}
public void Undelete(List items, bool silent = false)
@@ -295,7 +294,7 @@ namespace osu.Game.Beatmaps
return beatmapModelManager.Import(archive, lowPriority, cancellationToken);
}
- public Task?> Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public ILive? Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{
return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken);
}
@@ -312,9 +311,9 @@ namespace osu.Game.Beatmaps
// If we seem to be missing files, now is a good time to re-fetch.
if (importedBeatmap?.BeatmapSet?.Files.Count == 0)
{
- contextFactory.Run(realm =>
+ realm.Run(r =>
{
- var refetch = realm.Find(importedBeatmap.ID)?.Detach();
+ var refetch = r.Find(importedBeatmap.ID)?.Detach();
if (refetch != null)
importedBeatmap = refetch;
diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs
index ead86c1059..e8104f2ecb 100644
--- a/osu.Game/Beatmaps/BeatmapModelManager.cs
+++ b/osu.Game/Beatmaps/BeatmapModelManager.cs
@@ -33,8 +33,8 @@ namespace osu.Game.Beatmaps
protected override string[] HashableFileTypes => new[] { ".osu" };
- public BeatmapModelManager(RealmContextFactory contextFactory, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null)
- : base(contextFactory, storage, onlineLookupQueue)
+ public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null)
+ : base(realm, storage, onlineLookupQueue)
{
}
@@ -98,12 +98,12 @@ namespace osu.Game.Beatmaps
/// The first result for the provided query, or null if no results were found.
public BeatmapInfo? QueryBeatmap(Expression> query)
{
- return ContextFactory.Run(realm => realm.All().FirstOrDefault(query)?.Detach());
+ return Realm.Run(realm => realm.All().FirstOrDefault(query)?.Detach());
}
public void Update(BeatmapSetInfo item)
{
- ContextFactory.Write(realm =>
+ Realm.Write(realm =>
{
var existing = realm.Find(item.ID);
item.CopyChangesToRealm(existing);
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index ec20328fab..922439fcb8 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// The time signature at this control point.
///
- public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple };
+ public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignature.SimpleQuadruple);
///
/// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
@@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// The time signature at this control point.
///
- public TimeSignatures TimeSignature
+ public TimeSignature TimeSignature
{
get => TimeSignatureBindable.Value;
set => TimeSignatureBindable.Value = value;
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 893eb8ab78..8f3f05aa9f 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -340,9 +340,9 @@ namespace osu.Game.Beatmaps.Formats
double beatLength = Parsing.ParseDouble(split[1].Trim());
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
- TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
+ TimeSignature timeSignature = TimeSignature.SimpleQuadruple;
if (split.Length >= 3)
- timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]);
+ timeSignature = split[2][0] == '0' ? TimeSignature.SimpleQuadruple : new TimeSignature(Parsing.ParseInt(split[2]));
LegacySampleBank sampleSet = defaultSampleBank;
if (split.Length >= 4)
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index ebdc882d2f..9d848fd8a4 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -227,7 +227,7 @@ namespace osu.Game.Beatmaps.Formats
if (effectPoint.OmitFirstBarLine)
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
- writer.Write(FormattableString.Invariant($"{(int)legacyControlPoints.TimingPointAt(time).TimeSignature},"));
+ writer.Write(FormattableString.Invariant($"{legacyControlPoints.TimingPointAt(time).TimeSignature.Numerator},"));
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
@@ -242,12 +242,7 @@ namespace osu.Game.Beatmaps.Formats
yield break;
foreach (var hitObject in hitObjects)
- {
yield return hitObject.DifficultyControlPoint;
-
- foreach (var nested in collectDifficultyControlPoints(hitObject.NestedHitObjects))
- yield return nested;
- }
}
void extractDifficultyControlPoints(IEnumerable hitObjects)
diff --git a/osu.Game/Beatmaps/Timing/TimeSignature.cs b/osu.Game/Beatmaps/Timing/TimeSignature.cs
new file mode 100644
index 0000000000..eebbcc34cd
--- /dev/null
+++ b/osu.Game/Beatmaps/Timing/TimeSignature.cs
@@ -0,0 +1,45 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+
+namespace osu.Game.Beatmaps.Timing
+{
+ ///
+ /// Stores the time signature of a track.
+ /// For now, the lower numeral can only be 4; support for other denominators can be considered at a later date.
+ ///
+ public class TimeSignature : IEquatable
+ {
+ ///
+ /// The numerator of a signature.
+ ///
+ public int Numerator { get; }
+
+ // TODO: support time signatures with a denominator other than 4
+ // this in particular requires a new beatmap format.
+
+ public TimeSignature(int numerator)
+ {
+ if (numerator < 1)
+ throw new ArgumentOutOfRangeException(nameof(numerator), numerator, "The numerator of a time signature must be positive.");
+
+ Numerator = numerator;
+ }
+
+ public static TimeSignature SimpleTriple { get; } = new TimeSignature(3);
+ public static TimeSignature SimpleQuadruple { get; } = new TimeSignature(4);
+
+ public override string ToString() => $"{Numerator}/4";
+
+ public bool Equals(TimeSignature other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+
+ return Numerator == other.Numerator;
+ }
+
+ public override int GetHashCode() => Numerator;
+ }
+}
diff --git a/osu.Game/Beatmaps/Timing/TimeSignatures.cs b/osu.Game/Beatmaps/Timing/TimeSignatures.cs
index 33e6342ae6..d783d3f9ec 100644
--- a/osu.Game/Beatmaps/Timing/TimeSignatures.cs
+++ b/osu.Game/Beatmaps/Timing/TimeSignatures.cs
@@ -1,11 +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 System;
using System.ComponentModel;
namespace osu.Game.Beatmaps.Timing
{
- public enum TimeSignatures
+ [Obsolete("Use osu.Game.Beatmaps.Timing.TimeSignature instead.")]
+ public enum TimeSignatures // can be removed 20220722
{
[Description("4/4")]
SimpleQuadruple = 4,
diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs
index 6947752c47..d3f356bb24 100644
--- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Beatmaps
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
AudioManager IStorageResourceProvider.AudioManager => audioManager;
- RealmContextFactory IStorageResourceProvider.RealmContextFactory => null;
+ RealmAccess IStorageResourceProvider.RealmAccess => null;
IResourceStore IStorageResourceProvider.Files => files;
IResourceStore IStorageResourceProvider.Resources => resources;
IResourceStore IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore);
diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs
index 2bba20fb09..e5d2d572c8 100644
--- a/osu.Game/Configuration/SettingsStore.cs
+++ b/osu.Game/Configuration/SettingsStore.cs
@@ -10,11 +10,11 @@ namespace osu.Game.Configuration
// this class mostly exists as a wrapper to avoid breaking the ruleset API (see usage in RulesetConfigManager).
// it may cease to exist going forward, depending on how the structure of the config data layer changes.
- public readonly RealmContextFactory Realm;
+ public readonly RealmAccess Realm;
- public SettingsStore(RealmContextFactory realmFactory)
+ public SettingsStore(RealmAccess realm)
{
- Realm = realmFactory;
+ Realm = realm;
}
}
}
diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs
index bbbdac352e..adf91e4a41 100644
--- a/osu.Game/Database/EFToRealmMigrator.cs
+++ b/osu.Game/Database/EFToRealmMigrator.cs
@@ -27,13 +27,15 @@ namespace osu.Game.Database
{
internal class EFToRealmMigrator : CompositeDrawable
{
- public bool FinishedMigrating { get; private set; }
+ public Task MigrationCompleted => migrationCompleted.Task;
+
+ private readonly TaskCompletionSource migrationCompleted = new TaskCompletionSource();
[Resolved]
private DatabaseContextFactory efContextFactory { get; set; } = null!;
[Resolved]
- private RealmContextFactory realmContextFactory { get; set; } = null!;
+ private RealmAccess realm { get; set; } = null!;
[Resolved]
private OsuConfigManager config { get; set; } = null!;
@@ -99,6 +101,17 @@ namespace osu.Game.Database
{
using (var ef = efContextFactory.Get())
{
+ realm.Write(r =>
+ {
+ // Before beginning, ensure realm is in an empty state.
+ // Migrations which are half-completed could lead to issues if the user tries a second time.
+ // Note that we only do this for beatmaps and scores since the other migrations are yonks old.
+ r.RemoveAll();
+ r.RemoveAll();
+ r.RemoveAll();
+ r.RemoveAll();
+ });
+
migrateSettings(ef);
migrateSkins(ef);
migrateBeatmaps(ef);
@@ -114,7 +127,7 @@ namespace osu.Game.Database
Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important);
}, TaskCreationOptions.LongRunning).ContinueWith(t =>
{
- FinishedMigrating = true;
+ migrationCompleted.SetResult(true);
});
}
@@ -145,91 +158,82 @@ namespace osu.Game.Database
int count = existingBeatmapSets.Count();
- realmContextFactory.Run(realm =>
+ realm.Run(r =>
{
log($"Found {count} beatmaps in EF");
- // only migrate data if the realm database is empty.
- // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`.
- if (realm.All().Any(s => !s.Protected))
- {
- log("Skipping migration as realm already has beatmaps loaded");
- }
- else
- {
- var transaction = realm.BeginWrite();
- int written = 0;
+ var transaction = r.BeginWrite();
+ int written = 0;
- try
+ try
+ {
+ foreach (var beatmapSet in existingBeatmapSets)
{
- foreach (var beatmapSet in existingBeatmapSets)
+ if (++written % 1000 == 0)
{
- if (++written % 1000 == 0)
- {
- transaction.Commit();
- transaction = realm.BeginWrite();
- log($"Migrated {written}/{count} beatmaps...");
- }
+ transaction.Commit();
+ transaction = r.BeginWrite();
+ log($"Migrated {written}/{count} beatmaps...");
+ }
- var realmBeatmapSet = new BeatmapSetInfo
+ var realmBeatmapSet = new BeatmapSetInfo
+ {
+ OnlineID = beatmapSet.OnlineID ?? -1,
+ DateAdded = beatmapSet.DateAdded,
+ Status = beatmapSet.Status,
+ DeletePending = beatmapSet.DeletePending,
+ Hash = beatmapSet.Hash,
+ Protected = beatmapSet.Protected,
+ };
+
+ migrateFiles(beatmapSet, r, realmBeatmapSet);
+
+ foreach (var beatmap in beatmapSet.Beatmaps)
+ {
+ var ruleset = r.Find(beatmap.RulesetInfo.ShortName);
+ var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata);
+
+ var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata)
{
- OnlineID = beatmapSet.OnlineID ?? -1,
- DateAdded = beatmapSet.DateAdded,
- Status = beatmapSet.Status,
- DeletePending = beatmapSet.DeletePending,
- Hash = beatmapSet.Hash,
- Protected = beatmapSet.Protected,
+ DifficultyName = beatmap.DifficultyName,
+ Status = beatmap.Status,
+ OnlineID = beatmap.OnlineID ?? -1,
+ Length = beatmap.Length,
+ BPM = beatmap.BPM,
+ Hash = beatmap.Hash,
+ StarRating = beatmap.StarRating,
+ MD5Hash = beatmap.MD5Hash,
+ Hidden = beatmap.Hidden,
+ AudioLeadIn = beatmap.AudioLeadIn,
+ StackLeniency = beatmap.StackLeniency,
+ SpecialStyle = beatmap.SpecialStyle,
+ LetterboxInBreaks = beatmap.LetterboxInBreaks,
+ WidescreenStoryboard = beatmap.WidescreenStoryboard,
+ EpilepsyWarning = beatmap.EpilepsyWarning,
+ SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate,
+ DistanceSpacing = beatmap.DistanceSpacing,
+ BeatDivisor = beatmap.BeatDivisor,
+ GridSize = beatmap.GridSize,
+ TimelineZoom = beatmap.TimelineZoom,
+ Countdown = beatmap.Countdown,
+ CountdownOffset = beatmap.CountdownOffset,
+ MaxCombo = beatmap.MaxCombo,
+ Bookmarks = beatmap.Bookmarks,
+ BeatmapSet = realmBeatmapSet,
};
- migrateFiles(beatmapSet, realm, realmBeatmapSet);
-
- foreach (var beatmap in beatmapSet.Beatmaps)
- {
- var ruleset = realm.Find(beatmap.RulesetInfo.ShortName);
- var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata);
-
- var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata)
- {
- DifficultyName = beatmap.DifficultyName,
- Status = beatmap.Status,
- OnlineID = beatmap.OnlineID ?? -1,
- Length = beatmap.Length,
- BPM = beatmap.BPM,
- Hash = beatmap.Hash,
- StarRating = beatmap.StarRating,
- MD5Hash = beatmap.MD5Hash,
- Hidden = beatmap.Hidden,
- AudioLeadIn = beatmap.AudioLeadIn,
- StackLeniency = beatmap.StackLeniency,
- SpecialStyle = beatmap.SpecialStyle,
- LetterboxInBreaks = beatmap.LetterboxInBreaks,
- WidescreenStoryboard = beatmap.WidescreenStoryboard,
- EpilepsyWarning = beatmap.EpilepsyWarning,
- SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate,
- DistanceSpacing = beatmap.DistanceSpacing,
- BeatDivisor = beatmap.BeatDivisor,
- GridSize = beatmap.GridSize,
- TimelineZoom = beatmap.TimelineZoom,
- Countdown = beatmap.Countdown,
- CountdownOffset = beatmap.CountdownOffset,
- MaxCombo = beatmap.MaxCombo,
- Bookmarks = beatmap.Bookmarks,
- BeatmapSet = realmBeatmapSet,
- };
-
- realmBeatmapSet.Beatmaps.Add(realmBeatmap);
- }
-
- realm.Add(realmBeatmapSet);
+ realmBeatmapSet.Beatmaps.Add(realmBeatmap);
}
- }
- finally
- {
- transaction.Commit();
- }
- log($"Successfully migrated {count} beatmaps to realm");
+ r.Add(realmBeatmapSet);
+ }
}
+ finally
+ {
+ transaction.Commit();
+ }
+
+ log($"Successfully migrated {count} beatmaps to realm");
});
}
@@ -276,74 +280,66 @@ namespace osu.Game.Database
int count = existingScores.Count();
- realmContextFactory.Run(realm =>
+ realm.Run(r =>
{
log($"Found {count} scores in EF");
- // only migrate data if the realm database is empty.
- if (realm.All().Any())
- {
- log("Skipping migration as realm already has scores loaded");
- }
- else
- {
- var transaction = realm.BeginWrite();
- int written = 0;
+ var transaction = r.BeginWrite();
+ int written = 0;
- try
+ try
+ {
+ foreach (var score in existingScores)
{
- foreach (var score in existingScores)
+ if (++written % 1000 == 0)
{
- if (++written % 1000 == 0)
- {
- transaction.Commit();
- transaction = realm.BeginWrite();
- log($"Migrated {written}/{count} scores...");
- }
-
- var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash);
- var ruleset = realm.Find(score.Ruleset.ShortName);
- var user = new RealmUser
- {
- OnlineID = score.User.OnlineID,
- Username = score.User.Username
- };
-
- var realmScore = new ScoreInfo(beatmap, ruleset, user)
- {
- Hash = score.Hash,
- DeletePending = score.DeletePending,
- OnlineID = score.OnlineID ?? -1,
- ModsJson = score.ModsJson,
- StatisticsJson = score.StatisticsJson,
- TotalScore = score.TotalScore,
- MaxCombo = score.MaxCombo,
- Accuracy = score.Accuracy,
- HasReplay = ((IScoreInfo)score).HasReplay,
- Date = score.Date,
- PP = score.PP,
- Rank = score.Rank,
- HitEvents = score.HitEvents,
- Passed = score.Passed,
- Combo = score.Combo,
- Position = score.Position,
- Statistics = score.Statistics,
- Mods = score.Mods,
- APIMods = score.APIMods,
- };
-
- migrateFiles(score, realm, realmScore);
-
- realm.Add(realmScore);
+ transaction.Commit();
+ transaction = r.BeginWrite();
+ log($"Migrated {written}/{count} scores...");
}
- }
- finally
- {
- transaction.Commit();
- }
- log($"Successfully migrated {count} scores to realm");
+ var beatmap = r.All().First(b => b.Hash == score.BeatmapInfo.Hash);
+ var ruleset = r.Find(score.Ruleset.ShortName);
+ var user = new RealmUser
+ {
+ OnlineID = score.User.OnlineID,
+ Username = score.User.Username
+ };
+
+ var realmScore = new ScoreInfo(beatmap, ruleset, user)
+ {
+ Hash = score.Hash,
+ DeletePending = score.DeletePending,
+ OnlineID = score.OnlineID ?? -1,
+ ModsJson = score.ModsJson,
+ StatisticsJson = score.StatisticsJson,
+ TotalScore = score.TotalScore,
+ MaxCombo = score.MaxCombo,
+ Accuracy = score.Accuracy,
+ HasReplay = ((IScoreInfo)score).HasReplay,
+ Date = score.Date,
+ PP = score.PP,
+ Rank = score.Rank,
+ HitEvents = score.HitEvents,
+ Passed = score.Passed,
+ Combo = score.Combo,
+ Position = score.Position,
+ Statistics = score.Statistics,
+ Mods = score.Mods,
+ APIMods = score.APIMods,
+ };
+
+ migrateFiles(score, r, realmScore);
+
+ r.Add(realmScore);
+ }
}
+ finally
+ {
+ transaction.Commit();
+ }
+
+ log($"Successfully migrated {count} scores to realm");
});
}
@@ -373,13 +369,13 @@ namespace osu.Game.Database
break;
}
- realmContextFactory.Run(realm =>
+ realm.Run(r =>
{
- using (var transaction = realm.BeginWrite())
+ using (var transaction = r.BeginWrite())
{
// only migrate data if the realm database is empty.
- // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`.
- if (!realm.All().Any(s => !s.Protected))
+ // note that this cannot be written as: `r.All().All(s => s.Protected)`, because realm does not support `.All()`.
+ if (!r.All().Any(s => !s.Protected))
{
log($"Migrating {existingSkins.Count} skins");
@@ -394,9 +390,9 @@ namespace osu.Game.Database
InstantiationInfo = skin.InstantiationInfo,
};
- migrateFiles(skin, realm, realmSkin);
+ migrateFiles(skin, r, realmSkin);
- realm.Add(realmSkin);
+ r.Add(realmSkin);
if (skin.ID == userSkinInt)
userSkinChoice.Value = realmSkin.ID.ToString();
@@ -432,12 +428,12 @@ namespace osu.Game.Database
log("Beginning settings migration to realm");
- realmContextFactory.Run(realm =>
+ realm.Run(r =>
{
- using (var transaction = realm.BeginWrite())
+ using (var transaction = r.BeginWrite())
{
// only migrate data if the realm database is empty.
- if (!realm.All().Any())
+ if (!r.All().Any())
{
log($"Migrating {existingSettings.Count} settings");
@@ -451,7 +447,7 @@ namespace osu.Game.Database
if (string.IsNullOrEmpty(shortName))
continue;
- realm.Add(new RealmRulesetSetting
+ r.Add(new RealmRulesetSetting
{
Key = dkb.Key,
Value = dkb.StringValue,
diff --git a/osu.Game/Database/EmptyRealmSet.cs b/osu.Game/Database/EmptyRealmSet.cs
new file mode 100644
index 0000000000..b7f27ba035
--- /dev/null
+++ b/osu.Game/Database/EmptyRealmSet.cs
@@ -0,0 +1,46 @@
+// 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;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using Realms;
+using Realms.Schema;
+
+#nullable enable
+
+namespace osu.Game.Database
+{
+ public class EmptyRealmSet : IRealmCollection
+ {
+ private IList emptySet => Array.Empty();
+
+ public IEnumerator GetEnumerator() => emptySet.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => emptySet.GetEnumerator();
+ public int Count => emptySet.Count;
+ public T this[int index] => emptySet[index];
+ public int IndexOf(object item) => emptySet.IndexOf((T)item);
+ public bool Contains(object item) => emptySet.Contains((T)item);
+
+ public event NotifyCollectionChangedEventHandler? CollectionChanged
+ {
+ add => throw new NotImplementedException();
+ remove => throw new NotImplementedException();
+ }
+
+ public event PropertyChangedEventHandler? PropertyChanged
+ {
+ add => throw new NotImplementedException();
+ remove => throw new NotImplementedException();
+ }
+
+ public IRealmCollection Freeze() => throw new NotImplementedException();
+ public IDisposable SubscribeForNotifications(NotificationCallbackDelegate callback) => throw new NotImplementedException();
+ public bool IsValid => throw new NotImplementedException();
+ public Realm Realm => throw new NotImplementedException();
+ public ObjectSchema ObjectSchema => throw new NotImplementedException();
+ public bool IsFrozen => throw new NotImplementedException();
+ }
+}
diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs
index d00cfb2035..3047a1d30a 100644
--- a/osu.Game/Database/IModelImporter.cs
+++ b/osu.Game/Database/IModelImporter.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Database
/// An optional archive to use for model population.
/// Whether this is a low priority import.
/// An optional cancellation token.
- Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default);
+ ILive? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default);
///
/// A user displayable name for the model type associated with this manager.
diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmAccess.cs
similarity index 59%
rename from osu.Game/Database/RealmContextFactory.cs
rename to osu.Game/Database/RealmAccess.cs
index ea6a4b9636..4fdfaa804c 100644
--- a/osu.Game/Database/RealmContextFactory.cs
+++ b/osu.Game/Database/RealmAccess.cs
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -28,9 +30,9 @@ using Realms.Exceptions;
namespace osu.Game.Database
{
///
- /// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage.
+ /// A factory which provides safe access to the realm storage backend.
///
- public class RealmContextFactory : IDisposable
+ public class RealmAccess : IDisposable
{
private readonly Storage storage;
@@ -55,46 +57,75 @@ namespace osu.Game.Database
private const int schema_version = 13;
///
- /// Lock object which is held during sections, blocking context creation during blocking periods.
+ /// Lock object which is held during sections, blocking realm retrieval during blocking periods.
///
- private readonly SemaphoreSlim contextCreationLock = new SemaphoreSlim(1);
+ private readonly SemaphoreSlim realmRetrievalLock = new SemaphoreSlim(1);
- private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal();
+ private readonly ThreadLocal currentThreadCanCreateRealmInstances = new ThreadLocal();
- private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)");
+ ///
+ /// Holds a map of functions registered via and and a coinciding action which when triggered,
+ /// will unregister the subscription from realm.
+ ///
+ /// Put another way, the key is an action which registers the subscription with realm. The returned from the action is stored as the value and only
+ /// used internally.
+ ///
+ /// Entries in this dictionary are only removed when a consumer signals that the subscription should be permanently ceased (via their own ).
+ ///
+ private readonly Dictionary, IDisposable?> customSubscriptionsResetMap = new Dictionary, IDisposable?>();
- private readonly object contextLock = new object();
+ ///
+ /// Holds a map of functions registered via and a coinciding action which when triggered,
+ /// fires a change set event with an empty collection. This is used to inform subscribers when the main realm instance gets recycled, and ensure they don't use invalidated
+ /// managed realm objects from a previous firing.
+ ///
+ private readonly Dictionary, Action> notificationsResetMap = new Dictionary, Action>();
- private Realm? context;
+ private static readonly GlobalStatistic realm_instances_created = GlobalStatistics.Get(@"Realm", @"Instances (Created)");
- public Realm Context
+ private static readonly GlobalStatistic total_subscriptions = GlobalStatistics.Get(@"Realm", @"Subscriptions");
+
+ private readonly object realmLock = new object();
+
+ private Realm? updateRealm;
+
+ public Realm Realm => ensureUpdateRealm();
+
+ private Realm ensureUpdateRealm()
{
- get
+ if (!ThreadSafety.IsUpdateThread)
+ throw new InvalidOperationException(@$"Use {nameof(getRealmInstance)} when performing realm operations from a non-update thread");
+
+ lock (realmLock)
{
- if (!ThreadSafety.IsUpdateThread)
- throw new InvalidOperationException(@$"Use {nameof(Run)}/{nameof(Write)} when performing realm operations from a non-update thread");
-
- lock (contextLock)
+ if (updateRealm == null)
{
- if (context == null)
- {
- context = createContext();
- Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}");
- }
+ updateRealm = getRealmInstance();
- // creating a context will ensure our schema is up-to-date and migrated.
- return context;
+ Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}");
+
+ // Resubscribe any subscriptions
+ foreach (var action in customSubscriptionsResetMap.Keys)
+ registerSubscription(action);
}
+
+ Debug.Assert(updateRealm != null);
+
+ return updateRealm;
}
}
+ internal static bool CurrentThreadSubscriptionsAllowed => current_thread_subscriptions_allowed.Value;
+
+ private static readonly ThreadLocal current_thread_subscriptions_allowed = new ThreadLocal();
+
///
- /// Construct a new instance of a realm context factory.
+ /// Construct a new instance.
///
/// The game storage which will be used to create the realm backing file.
/// The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified.
/// An EF factory used only for migration purposes.
- public RealmContextFactory(Storage storage, string filename, IDatabaseContextFactory? efContextFactory = null)
+ public RealmAccess(Storage storage, string filename, IDatabaseContextFactory? efContextFactory = null)
{
this.storage = storage;
this.efContextFactory = efContextFactory;
@@ -108,7 +139,7 @@ namespace osu.Game.Database
try
{
- // This method triggers the first `CreateContext` call, which will implicitly run realm migrations and bring the schema up-to-date.
+ // This method triggers the first `getRealmInstance` call, which will implicitly run realm migrations and bring the schema up-to-date.
cleanupPendingDeletions();
}
catch (Exception e)
@@ -124,7 +155,7 @@ namespace osu.Game.Database
private void cleanupPendingDeletions()
{
- using (var realm = createContext())
+ using (var realm = getRealmInstance())
using (var transaction = realm.BeginWrite())
{
var pendingDeleteScores = realm.All().Where(s => s.DeletePending);
@@ -172,34 +203,28 @@ namespace osu.Game.Database
///
/// Run work on realm with a return value.
///
- ///
- /// Handles correct context management automatically.
- ///
/// The work to run.
/// The return type.
public T Run(Func action)
{
if (ThreadSafety.IsUpdateThread)
- return action(Context);
+ return action(Realm);
- using (var realm = createContext())
+ using (var realm = getRealmInstance())
return action(realm);
}
///
/// Run work on realm.
///
- ///
- /// Handles correct context management automatically.
- ///
/// The work to run.
public void Run(Action action)
{
if (ThreadSafety.IsUpdateThread)
- action(Context);
+ action(Realm);
else
{
- using (var realm = createContext())
+ using (var realm = getRealmInstance())
action(realm);
}
}
@@ -207,44 +232,155 @@ namespace osu.Game.Database
///
/// Write changes to realm.
///
- ///
- /// Handles correct context management and transaction committing automatically.
- ///
/// The work to run.
public void Write(Action action)
{
if (ThreadSafety.IsUpdateThread)
- Context.Write(action);
+ Realm.Write(action);
else
{
- using (var realm = createContext())
+ using (var realm = getRealmInstance())
realm.Write(action);
}
}
- private Realm createContext()
+ ///
+ /// Subscribe to a realm collection and begin watching for asynchronous changes.
+ ///
+ ///
+ /// This adds osu! specific thread and managed state safety checks on top of .
+ ///
+ /// In addition to the documented realm behaviour, we have the additional requirement of handling subscriptions over potential realm instance recycle.
+ /// When this happens, callback events will be automatically fired:
+ /// - On recycle start, a callback with an empty collection and null will be invoked.
+ /// - On recycle end, a standard initial realm callback will arrive, with null and an up-to-date collection.
+ ///
+ /// The to observe for changes.
+ /// Type of the elements in the list.
+ /// The callback to be invoked with the updated .
+ ///
+ /// A subscription token. It must be kept alive for as long as you want to receive change notifications.
+ /// To stop receiving notifications, call .
+ ///
+ ///
+ public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate callback)
+ where T : RealmObjectBase
+ {
+ if (!ThreadSafety.IsUpdateThread)
+ throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread.");
+
+ lock (realmLock)
+ {
+ Func action = realm => query(realm).QueryAsyncWithNotifications(callback);
+
+ // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing.
+ notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null));
+ return RegisterCustomSubscription(action);
+ }
+ }
+
+ ///
+ /// Run work on realm that will be run every time the update thread realm instance gets recycled.
+ ///
+ /// The work to run. Return value should be an from QueryAsyncWithNotifications, or an to clean up any bindings.
+ /// An which should be disposed to unsubscribe any inner subscription.
+ public IDisposable RegisterCustomSubscription(Func action)
+ {
+ if (!ThreadSafety.IsUpdateThread)
+ throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread.");
+
+ var syncContext = SynchronizationContext.Current;
+
+ total_subscriptions.Value++;
+
+ registerSubscription(action);
+
+ // This token is returned to the consumer.
+ // When disposed, it will cause the registration to be permanently ceased (unsubscribed with realm and unregistered by this class).
+ return new InvokeOnDisposal(() =>
+ {
+ if (ThreadSafety.IsUpdateThread)
+ unsubscribe();
+ else
+ syncContext.Post(_ => unsubscribe(), null);
+
+ void unsubscribe()
+ {
+ lock (realmLock)
+ {
+ if (customSubscriptionsResetMap.TryGetValue(action, out var unsubscriptionAction))
+ {
+ unsubscriptionAction?.Dispose();
+ customSubscriptionsResetMap.Remove(action);
+ notificationsResetMap.Remove(action);
+ total_subscriptions.Value--;
+ }
+ }
+ }
+ });
+ }
+
+ private void registerSubscription(Func action)
+ {
+ Debug.Assert(ThreadSafety.IsUpdateThread);
+
+ lock (realmLock)
+ {
+ // Retrieve realm instance outside of flag update to ensure that the instance is retrieved,
+ // as attempting to access it inside the subscription if it's not constructed would lead to
+ // cyclic invocations of the subscription callback.
+ var realm = Realm;
+
+ Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null);
+
+ current_thread_subscriptions_allowed.Value = true;
+ customSubscriptionsResetMap[action] = action(realm);
+ current_thread_subscriptions_allowed.Value = false;
+ }
+ }
+
+ ///
+ /// Unregister all subscriptions when the realm instance is to be recycled.
+ /// Subscriptions will still remain and will be re-subscribed when the realm instance returns.
+ ///
+ private void unregisterAllSubscriptions()
+ {
+ lock (realmLock)
+ {
+ foreach (var action in notificationsResetMap.Values)
+ action();
+
+ foreach (var action in customSubscriptionsResetMap)
+ {
+ action.Value?.Dispose();
+ customSubscriptionsResetMap[action.Key] = null;
+ }
+ }
+ }
+
+ private Realm getRealmInstance()
{
if (isDisposed)
- throw new ObjectDisposedException(nameof(RealmContextFactory));
+ throw new ObjectDisposedException(nameof(RealmAccess));
bool tookSemaphoreLock = false;
try
{
- if (!currentThreadCanCreateContexts.Value)
+ if (!currentThreadCanCreateRealmInstances.Value)
{
- contextCreationLock.Wait();
- currentThreadCanCreateContexts.Value = true;
+ realmRetrievalLock.Wait();
+ currentThreadCanCreateRealmInstances.Value = true;
tookSemaphoreLock = true;
}
else
{
- // the semaphore is used to handle blocking of all context creation during certain periods.
- // once the semaphore has been taken by this code section, it is safe to create further contexts on the same thread.
- // this can happen if a realm subscription is active and triggers a callback which has user code that calls `CreateContext`.
+ // the semaphore is used to handle blocking of all realm retrieval during certain periods.
+ // once the semaphore has been taken by this code section, it is safe to retrieve further realm instances on the same thread.
+ // this can happen if a realm subscription is active and triggers a callback which has user code that calls `Run`.
}
- contexts_created.Value++;
+ realm_instances_created.Value++;
return Realm.GetInstance(getConfiguration());
}
@@ -252,8 +388,8 @@ namespace osu.Game.Database
{
if (tookSemaphoreLock)
{
- contextCreationLock.Release();
- currentThreadCanCreateContexts.Value = false;
+ realmRetrievalLock.Release();
+ currentThreadCanCreateRealmInstances.Value = false;
}
}
}
@@ -442,7 +578,7 @@ namespace osu.Game.Database
}
///
- /// Flush any active contexts and block any further writes.
+ /// Flush any active realm instances and block any further writes.
///
///
/// This should be used in places we need to ensure no ongoing reads/writes are occurring with realm.
@@ -452,21 +588,36 @@ namespace osu.Game.Database
public IDisposable BlockAllOperations()
{
if (isDisposed)
- throw new ObjectDisposedException(nameof(RealmContextFactory));
+ throw new ObjectDisposedException(nameof(RealmAccess));
+
+ SynchronizationContext? syncContext = null;
try
{
- contextCreationLock.Wait();
+ realmRetrievalLock.Wait();
- lock (contextLock)
+ lock (realmLock)
{
- if (!ThreadSafety.IsUpdateThread && context != null)
- throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread.");
+ if (updateRealm == null)
+ {
+ // null realm means the update thread has not yet retrieved its instance.
+ // we don't need to worry about reviving the update instance in this case, so don't bother with the SynchronizationContext.
+ Debug.Assert(!ThreadSafety.IsUpdateThread);
+ }
+ else
+ {
+ if (!ThreadSafety.IsUpdateThread)
+ throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread.");
+
+ syncContext = SynchronizationContext.Current;
+ }
+
+ unregisterAllSubscriptions();
Logger.Log(@"Blocking realm operations.", LoggingTarget.Database);
- context?.Dispose();
- context = null;
+ updateRealm?.Dispose();
+ updateRealm = null;
}
const int sleep_length = 200;
@@ -493,15 +644,19 @@ namespace osu.Game.Database
}
catch
{
- contextCreationLock.Release();
+ restoreOperation();
throw;
}
- return new InvokeOnDisposal(this, factory =>
+ return new InvokeOnDisposal(restoreOperation);
+
+ void restoreOperation()
{
- factory.contextCreationLock.Release();
Logger.Log(@"Restoring realm operations.", LoggingTarget.Database);
- });
+ realmRetrievalLock.Release();
+ // Post back to the update thread to revive any subscriptions.
+ syncContext?.Post(_ => ensureUpdateRealm(), null);
+ }
}
// https://github.com/realm/realm-dotnet/blob/32f4ebcc88b3e80a3b254412665340cd9f3bd6b5/Realm/Realm/Extensions/ReflectionExtensions.cs#L46
@@ -511,16 +666,16 @@ namespace osu.Game.Database
public void Dispose()
{
- lock (contextLock)
+ lock (realmLock)
{
- context?.Dispose();
+ updateRealm?.Dispose();
}
if (!isDisposed)
{
- // intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal.
- contextCreationLock.Wait();
- contextCreationLock.Dispose();
+ // intentionally block realm retrieval indefinitely. this ensures that nothing can start consuming a new instance after disposal.
+ realmRetrievalLock.Wait();
+ realmRetrievalLock.Dispose();
isDisposed = true;
}
diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs
index df5e165f8e..f06ba1eff9 100644
--- a/osu.Game/Database/RealmLive.cs
+++ b/osu.Game/Database/RealmLive.cs
@@ -24,17 +24,17 @@ namespace osu.Game.Database
///
private readonly T data;
- private readonly RealmContextFactory realmFactory;
+ private readonly RealmAccess realm;
///
/// Construct a new instance of live realm data.
///
/// The realm data.
- /// The realm factory the data was sourced from. May be null for an unmanaged object.
- public RealmLive(T data, RealmContextFactory realmFactory)
+ /// The realm factory the data was sourced from. May be null for an unmanaged object.
+ public RealmLive(T data, RealmAccess realm)
{
this.data = data;
- this.realmFactory = realmFactory;
+ this.realm = realm;
ID = data.ID;
}
@@ -51,10 +51,7 @@ namespace osu.Game.Database
return;
}
- realmFactory.Run(realm =>
- {
- perform(retrieveFromID(realm, ID));
- });
+ realm.Run(r => perform(retrieveFromID(r, ID)));
}
///
@@ -66,9 +63,9 @@ namespace osu.Game.Database
if (!IsManaged)
return perform(data);
- return realmFactory.Run(realm =>
+ return realm.Run(r =>
{
- var returnData = perform(retrieveFromID(realm, ID));
+ var returnData = perform(retrieveFromID(r, ID));
if (returnData is RealmObjectBase realmObject && realmObject.IsManaged)
throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}.");
@@ -104,7 +101,7 @@ namespace osu.Game.Database
if (!ThreadSafety.IsUpdateThread)
throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads");
- return realmFactory.Context.Find(ID);
+ return realm.Realm.Find(ID);
}
}
diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs
index c25aeab336..d4f8978ac5 100644
--- a/osu.Game/Database/RealmObjectExtensions.cs
+++ b/osu.Game/Database/RealmObjectExtensions.cs
@@ -7,7 +7,6 @@ using System.Linq;
using System.Runtime.Serialization;
using AutoMapper;
using AutoMapper.Internal;
-using osu.Framework.Development;
using osu.Game.Beatmaps;
using osu.Game.Input.Bindings;
using osu.Game.Models;
@@ -217,16 +216,16 @@ namespace osu.Game.Database
return new RealmLiveUnmanaged(realmObject);
}
- public static List> ToLive(this IEnumerable realmList, RealmContextFactory realmContextFactory)
+ public static List> ToLive(this IEnumerable realmList, RealmAccess realm)
where T : RealmObject, IHasGuidPrimaryKey
{
- return realmList.Select(l => new RealmLive(l, realmContextFactory)).Cast>().ToList();
+ return realmList.Select(l => new RealmLive(l, realm)).Cast>().ToList();
}
- public static ILive ToLive(this T realmObject, RealmContextFactory realmContextFactory)
+ public static ILive ToLive(this T realmObject, RealmAccess realm)
where T : RealmObject, IHasGuidPrimaryKey
{
- return new RealmLive(realmObject, realmContextFactory);
+ return new RealmLive(realmObject, realm);
}
///
@@ -272,9 +271,8 @@ namespace osu.Game.Database
public static IDisposable? QueryAsyncWithNotifications(this IRealmCollection collection, NotificationCallbackDelegate callback)
where T : RealmObjectBase
{
- // Subscriptions can only work on the main thread.
- if (!ThreadSafety.IsUpdateThread)
- throw new InvalidOperationException("Cannot subscribe for realm notifications from a non-update thread.");
+ if (!RealmAccess.CurrentThreadSubscriptionsAllowed)
+ throw new InvalidOperationException($"Make sure to call {nameof(RealmAccess)}.{nameof(RealmAccess.RegisterForNotifications)}");
return collection.SubscribeForNotifications(callback);
}
diff --git a/osu.Game/IO/IStorageResourceProvider.cs b/osu.Game/IO/IStorageResourceProvider.cs
index 950b5aae09..b381ac70b0 100644
--- a/osu.Game/IO/IStorageResourceProvider.cs
+++ b/osu.Game/IO/IStorageResourceProvider.cs
@@ -28,7 +28,7 @@ namespace osu.Game.IO
///
/// Access realm.
///
- RealmContextFactory RealmContextFactory { get; }
+ RealmAccess RealmAccess { get; }
///
/// Create a texture loader store based on an underlying data store.
diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
index 03b069d431..ba129b93e5 100644
--- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
+++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Input.Bindings;
using osu.Game.Database;
using osu.Game.Rulesets;
+using Realms;
namespace osu.Game.Input.Bindings
{
@@ -23,10 +24,9 @@ namespace osu.Game.Input.Bindings
private readonly int? variant;
private IDisposable realmSubscription;
- private IQueryable realmKeyBindings;
[Resolved]
- private RealmContextFactory realmFactory { get; set; }
+ private RealmAccess realm { get; set; }
public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0);
@@ -49,32 +49,26 @@ namespace osu.Game.Input.Bindings
protected override void LoadComplete()
{
- string rulesetName = ruleset?.ShortName;
-
- realmKeyBindings = realmFactory.Context.All()
- .Where(b => b.RulesetName == rulesetName && b.Variant == variant);
-
- realmSubscription = realmKeyBindings
- .QueryAsyncWithNotifications((sender, changes, error) =>
- {
- // first subscription ignored as we are handling this in LoadComplete.
- if (changes == null)
- return;
-
- ReloadMappings();
- });
+ realmSubscription = realm.RegisterForNotifications(queryRealmKeyBindings, (sender, changes, error) =>
+ {
+ // The first fire of this is a bit redundant as this is being called in base.LoadComplete,
+ // but this is safest in case the subscription is restored after a context recycle.
+ reloadMappings(sender.AsQueryable());
+ });
base.LoadComplete();
}
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
+ protected override void ReloadMappings() => reloadMappings(queryRealmKeyBindings(realm.Realm));
- realmSubscription?.Dispose();
+ private IQueryable queryRealmKeyBindings(Realm realm)
+ {
+ string rulesetName = ruleset?.ShortName;
+ return realm.All()
+ .Where(b => b.RulesetName == rulesetName && b.Variant == variant);
}
- protected override void ReloadMappings()
+ private void reloadMappings(IQueryable realmKeyBindings)
{
var defaults = DefaultKeyBindings.ToList();
@@ -93,5 +87,12 @@ namespace osu.Game.Input.Bindings
else
KeyBindings = newBindings;
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ realmSubscription?.Dispose();
+ }
}
}
diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs
index 60f7eb2198..20971ffca5 100644
--- a/osu.Game/Input/RealmKeyBindingStore.cs
+++ b/osu.Game/Input/RealmKeyBindingStore.cs
@@ -16,12 +16,12 @@ namespace osu.Game.Input
{
public class RealmKeyBindingStore
{
- private readonly RealmContextFactory realmFactory;
+ private readonly RealmAccess realm;
private readonly ReadableKeyCombinationProvider keyCombinationProvider;
- public RealmKeyBindingStore(RealmContextFactory realmFactory, ReadableKeyCombinationProvider keyCombinationProvider)
+ public RealmKeyBindingStore(RealmAccess realm, ReadableKeyCombinationProvider keyCombinationProvider)
{
- this.realmFactory = realmFactory;
+ this.realm = realm;
this.keyCombinationProvider = keyCombinationProvider;
}
@@ -34,7 +34,7 @@ namespace osu.Game.Input
{
List combinations = new List