mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 08:02:55 +08:00
Merge branch 'master' into improve-timeline-zoom
This commit is contained in:
commit
bdf215c576
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -52,7 +52,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.115.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.118.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.125.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<BeatmapSetInfo>().First();
|
||||
var beatmapSet = r.All<BeatmapSetInfo>().First();
|
||||
|
||||
for (int i = 0; i < ReadsPerFetch; i++)
|
||||
{
|
||||
@ -61,7 +61,7 @@ namespace osu.Game.Benchmarks
|
||||
{
|
||||
try
|
||||
{
|
||||
var beatmapSet = realmFactory.Context.All<BeatmapSetInfo>().First();
|
||||
var beatmapSet = realm.Realm.All<BeatmapSetInfo>().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<BeatmapSetInfo>().First().ToLive(realmFactory);
|
||||
var beatmapSet = r.All<BeatmapSetInfo>().First().ToLive(realm);
|
||||
|
||||
for (int i = 0; i < ReadsPerFetch; i++)
|
||||
{
|
||||
@ -100,7 +100,7 @@ namespace osu.Game.Benchmarks
|
||||
{
|
||||
try
|
||||
{
|
||||
var beatmapSet = realmFactory.Context.All<BeatmapSetInfo>().First().ToLive(realmFactory);
|
||||
var beatmapSet = realm.Realm.All<BeatmapSetInfo>().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<BeatmapSetInfo>().First().Detach();
|
||||
var beatmapSet = r.All<BeatmapSetInfo>().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();
|
||||
}
|
||||
|
@ -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<float> SizeMultiplier { get; } = new BindableNumber<float>
|
||||
{
|
||||
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<int> 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";
|
||||
|
@ -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<float> SizeMultiplier { get; } = new BindableNumber<float>
|
||||
{
|
||||
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<int> e)
|
||||
{
|
||||
this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
||||
}
|
||||
|
||||
protected override string FragmentShader => "RectangularFlashlight";
|
||||
|
@ -0,0 +1,98 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<EditorBeatmap>().FirstOrDefault();
|
||||
|
||||
private EditorClock editorClock => editor.ChildrenOfType<EditorClock>().FirstOrDefault();
|
||||
|
||||
private Slider slider => editorBeatmap.HitObjects.OfType<Slider>().FirstOrDefault();
|
||||
|
||||
private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType<TimelineHitObjectBlueprint>().FirstOrDefault();
|
||||
|
||||
private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType<DifficultyPointPiece>().First();
|
||||
|
||||
private IndeterminateSliderWithTextBoxInput<double> velocityTextBox => Game.ChildrenOfType<DifficultyPointPiece.DifficultyEditPopover>().First().ChildrenOfType<IndeterminateSliderWithTextBoxInput<double>>().First();
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
private bool editorComponentsReady => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true
|
||||
&& editor.ChildrenOfType<TimelineArea>().FirstOrDefault()?.IsLoaded == true
|
||||
&& editor?.ChildrenOfType<Playfield>().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<Playfield>().First().ScreenSpaceDrawQuad.Centre));
|
||||
AddStep("start placement", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType<Playfield>().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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
base.ApplyToDrawableRuleset(drawableRuleset);
|
||||
|
||||
flashlight.FollowDelay = FollowDelay.Value;
|
||||
}
|
||||
|
||||
[SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")]
|
||||
public BindableNumber<double> 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<float> SizeMultiplier { get; } = new BindableNumber<float>
|
||||
{
|
||||
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<bool> 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<int> 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";
|
||||
|
@ -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<float> SizeMultiplier { get; } = new BindableNumber<float>
|
||||
{
|
||||
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<int> e)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -53,9 +53,9 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
|
||||
{
|
||||
var realmContextFactory = osu.Dependencies.Get<RealmContextFactory>();
|
||||
var realm = osu.Dependencies.Get<RealmAccess>();
|
||||
|
||||
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();
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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<BeatmapSetInfo>? 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<BeatmapSetInfo>? 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<BeatmapSetInfo>? imported;
|
||||
|
||||
using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
|
||||
imported = await importer.Import(reader);
|
||||
|
||||
Assert.AreEqual(1, realmFactory.Context.All<BeatmapSetInfo>().Count());
|
||||
Assert.AreEqual(1, realm.Realm.All<BeatmapSetInfo>().Count());
|
||||
|
||||
Assert.NotNull(imported);
|
||||
Debug.Assert(imported != null);
|
||||
|
||||
imported.PerformWrite(s => s.DeletePending = true);
|
||||
|
||||
Assert.AreEqual(1, realmFactory.Context.All<BeatmapSetInfo>().Count(s => s.DeletePending));
|
||||
Assert.AreEqual(1, realm.Realm.All<BeatmapSetInfo>().Count(s => s.DeletePending));
|
||||
}
|
||||
});
|
||||
|
||||
Logger.Log("Running with no work to purge pending deletions");
|
||||
|
||||
RunTestWithRealm((realmFactory, _) => { Assert.AreEqual(0, realmFactory.Context.All<BeatmapSetInfo>().Count()); });
|
||||
RunTestWithRealm((realm, _) => { Assert.AreEqual(0, realm.Realm.All<BeatmapSetInfo>().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<BeatmapSetInfo>().First(beatmapSet => beatmapSet.ID == importedSet.ID);
|
||||
var imported = realm.Realm.All<BeatmapSetInfo>().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<RulesetInfo>().First();
|
||||
var ruleset = realm.Realm.All<RulesetInfo>().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<BeatmapSetInfo>().First();
|
||||
BeatmapSetInfo setToUpdate = realm.Realm.All<BeatmapSetInfo>().First();
|
||||
|
||||
var beatmapToUpdate = setToUpdate.Beatmaps.First();
|
||||
|
||||
realmFactory.Context.Write(() => beatmapToUpdate.DifficultyName = "updated");
|
||||
realm.Realm.Write(() => beatmapToUpdate.DifficultyName = "updated");
|
||||
|
||||
BeatmapInfo updatedInfo = realmFactory.Context.All<BeatmapInfo>().First(b => b.ID == beatmapToUpdate.ID);
|
||||
BeatmapInfo updatedInfo = realm.Realm.All<BeatmapInfo>().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)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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<BeatmapInfo>().QueryAsyncWithNotifications((sender, changes, error) =>
|
||||
var subscription = r.All<BeatmapInfo>().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<TimeoutException>(() =>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,11 @@ namespace osu.Game.Tests.Database
|
||||
[Test]
|
||||
public void TestLiveEquality()
|
||||
{
|
||||
RunTestWithRealm((realmFactory, _) =>
|
||||
RunTestWithRealm((realm, _) =>
|
||||
{
|
||||
ILive<BeatmapInfo> beatmap = realmFactory.Run(realm => realm.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory));
|
||||
ILive<BeatmapInfo> beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm));
|
||||
|
||||
ILive<BeatmapInfo> beatmap2 = realmFactory.Run(realm => realm.All<BeatmapInfo>().First().ToLive(realmFactory));
|
||||
ILive<BeatmapInfo> beatmap2 = realm.Run(r => r.All<BeatmapInfo>().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<BeatmapInfo>? 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<BeatmapInfo>? 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<BeatmapInfo>? 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<BeatmapInfo>? 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<InvalidOperationException>(() =>
|
||||
{
|
||||
@ -207,16 +207,16 @@ namespace osu.Game.Tests.Database
|
||||
[Test]
|
||||
public void TestValueAccessWithoutOpenContextFails()
|
||||
{
|
||||
RunTestWithRealm((realmFactory, _) =>
|
||||
RunTestWithRealm((realm, _) =>
|
||||
{
|
||||
ILive<BeatmapInfo>? 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<BeatmapInfo>().QueryAsyncWithNotifications(gotChange);
|
||||
ILive<BeatmapInfo>? 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<BeatmapInfo> sender, ChangeSet changes, Exception error)
|
||||
|
138
osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
Normal file
138
osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
Normal file
@ -0,0 +1,138 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<BeatmapSetInfo>? resolvedItems = null;
|
||||
ChangeSet? lastChanges = null;
|
||||
|
||||
RunTestWithRealm((realm, _) =>
|
||||
{
|
||||
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
||||
|
||||
var registration = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>(), 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<BeatmapSetInfo>();
|
||||
r.RemoveAll<RulesetInfo>();
|
||||
});
|
||||
|
||||
realm.Run(r => r.Refresh());
|
||||
|
||||
if (shouldArrive)
|
||||
Assert.That(lastChanges?.DeletedIndices, Has.One.Items);
|
||||
else
|
||||
Assert.That(lastChanges, Is.Null);
|
||||
}
|
||||
});
|
||||
|
||||
void onChanged(IRealmCollection<BeatmapSetInfo> 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<BeatmapSetInfo>().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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ namespace osu.Game.Tests.Database
|
||||
storage.DeleteDirectory(string.Empty);
|
||||
}
|
||||
|
||||
protected void RunTestWithRealm(Action<RealmContextFactory, OsuStorage> testAction, [CallerMemberName] string caller = "")
|
||||
protected void RunTestWithRealm(Action<RealmAccess, OsuStorage> 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<RealmContextFactory, Storage, Task> testAction, [CallerMemberName] string caller = "")
|
||||
protected void RunTestWithRealmAsync(Func<RealmAccess, Storage, Task> 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
|
||||
|
@ -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<RulesetInfo>().Count());
|
||||
Assert.AreEqual(4, realm.Realm.All<RulesetInfo>().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<RulesetInfo>().Count());
|
||||
Assert.AreEqual(4, realm.Realm.All<RulesetInfo>().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);
|
||||
|
@ -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<RealmKeyBinding>();
|
||||
var results = r.All<RealmKeyBinding>();
|
||||
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<RulesetInfo>());
|
||||
|
||||
realmContextFactory.Run(outerRealm =>
|
||||
realm.Run(outerRealm =>
|
||||
{
|
||||
var backBinding = outerRealm.All<RealmKeyBinding>().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);
|
||||
}
|
||||
|
||||
|
@ -261,7 +261,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
public AudioManager AudioManager => Audio;
|
||||
public IResourceStore<byte[]> Files => null;
|
||||
public new IResourceStore<byte[]> Resources => base.Resources;
|
||||
public RealmContextFactory RealmContextFactory => null;
|
||||
public RealmAccess RealmAccess => null;
|
||||
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
|
||||
|
||||
#endregion
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}"));
|
||||
|
@ -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<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.CacheAs<BeatmapModelDownloader>(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<BeatmapSetInfo>());
|
||||
ContextFactory.Write(r => r.RemoveAll<BeatmapInfo>());
|
||||
Realm.Write(r => r.RemoveAll<BeatmapSetInfo>());
|
||||
Realm.Write(r => r.RemoveAll<BeatmapInfo>());
|
||||
|
||||
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<bool> AllowImport = new TaskCompletionSource<bool>();
|
||||
|
||||
public Task<ILive<BeatmapSetInfo>> CurrentImportTask { get; private set; }
|
||||
public ILive<BeatmapSetInfo> CurrentImport { get; private set; }
|
||||
|
||||
public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> 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<byte[]> 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<ILive<BeatmapSetInfo>> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
|
||||
public override ILive<BeatmapSetInfo> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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<ScoreInfo> 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<ScoreManager>();
|
||||
await scoreManager.Import(score, archive);
|
||||
|
||||
scoreManager.Import(score, archive);
|
||||
|
||||
return scoreManager.Query(_ => true);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true);
|
||||
|
||||
AddStep("Set beat divisor", () => editor.Dependencies.Get<BindableBeatDivisor>().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");
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -0,0 +1,88 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<OsuTextBox>().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");
|
||||
}
|
||||
}
|
||||
}
|
@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
Add(new ModNightcore<HitObject>.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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<OsuPasswordTextBox>().First().Text = "password");
|
||||
AddStep("submit", () => loginPanel.ChildrenOfType<OsuButton>().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<OsuPasswordTextBox>().First().Text = "password");
|
||||
AddStep("submit", () => loginPanel.ChildrenOfType<OsuButton>().First(b => b.Text.ToString() == "Sign in").TriggerClick());
|
||||
|
||||
AddStep("click on flag", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(loginPanel.ChildrenOfType<UpdateableFlag>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("hide requested", () => hideCount == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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", () =>
|
||||
{
|
||||
|
@ -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()
|
||||
|
@ -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()));
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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<BeatmapInfo>();
|
||||
|
||||
@ -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()
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
.ChildrenOfType<KeyBindingPanel>().SingleOrDefault();
|
||||
|
||||
private RealmKeyBinding firstOsuRulesetKeyBindings => Game.Dependencies
|
||||
.Get<RealmContextFactory>().Context
|
||||
.Get<RealmAccess>().Realm
|
||||
.All<RealmKeyBinding>()
|
||||
.AsEnumerable()
|
||||
.First(k => k.RulesetName == "osu" && k.ActionInt == 0);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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<BeatmapInfo>()
|
||||
.Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0)
|
||||
.FirstOrDefault();
|
||||
var beatmapInfo = r.All<BeatmapInfo>()
|
||||
.Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (beatmapInfo != null)
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
||||
|
@ -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<AudioManager>(), Resources, dependencies.Get<GameHost>(), 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<AudioManager>(), Resources, dependencies.Get<GameHost>(), 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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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<BeatmapSetInfo> beatmapSets) => beatmapSets.All(set => set != null);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get<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<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get<RulesetStore>(), () => 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<ScoreInfo>().Where(s => s.DeletePending).ToList());
|
||||
scoreManager.Undelete(r.All<ScoreInfo>().Where(s => s.DeletePending).ToList());
|
||||
});
|
||||
|
||||
leaderboard.Scores = null;
|
||||
|
@ -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");
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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<byte[]> gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false)
|
||||
public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore<byte[]> 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);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="WorkingBeatmap"/>.
|
||||
@ -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
|
||||
/// <param name="beatmapInfo">The beatmap difficulty to hide.</param>
|
||||
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>(beatmapInfo.ID);
|
||||
beatmapInfo = r.Find<BeatmapInfo>(beatmapInfo.ID);
|
||||
|
||||
beatmapInfo.Hidden = true;
|
||||
transaction.Commit();
|
||||
@ -138,12 +137,12 @@ namespace osu.Game.Beatmaps
|
||||
/// <param name="beatmapInfo">The beatmap difficulty to restore.</param>
|
||||
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>(beatmapInfo.ID);
|
||||
beatmapInfo = r.Find<BeatmapInfo>(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<BeatmapInfo>().Where(b => b.Hidden))
|
||||
foreach (var beatmap in r.All<BeatmapInfo>().Where(b => b.Hidden))
|
||||
beatmap.Hidden = false;
|
||||
|
||||
transaction.Commit();
|
||||
@ -171,10 +170,10 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
|
||||
public List<BeatmapSetInfo> GetAllUsableBeatmapSets()
|
||||
{
|
||||
return contextFactory.Run(realm =>
|
||||
return realm.Run(r =>
|
||||
{
|
||||
realm.Refresh();
|
||||
return realm.All<BeatmapSetInfo>().Where(b => !b.DeletePending).Detach();
|
||||
r.Refresh();
|
||||
return r.All<BeatmapSetInfo>().Where(b => !b.DeletePending).Detach();
|
||||
});
|
||||
}
|
||||
|
||||
@ -185,7 +184,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||
public ILive<BeatmapSetInfo>? QueryBeatmapSet(Expression<Func<BeatmapSetInfo, bool>> query)
|
||||
{
|
||||
return contextFactory.Run(realm => realm.All<BeatmapSetInfo>().FirstOrDefault(query)?.ToLive(contextFactory));
|
||||
return realm.Run(r => r.All<BeatmapSetInfo>().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<Func<BeatmapSetInfo, bool>>? filter = null, bool silent = false)
|
||||
{
|
||||
contextFactory.Run(realm =>
|
||||
realm.Run(r =>
|
||||
{
|
||||
var items = realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected);
|
||||
var items = r.All<BeatmapSetInfo>().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<BeatmapSetInfo>().Where(s => s.DeletePending).ToList()));
|
||||
realm.Run(r => beatmapModelManager.Undelete(r.All<BeatmapSetInfo>().Where(s => s.DeletePending).ToList()));
|
||||
}
|
||||
|
||||
public void Undelete(List<BeatmapSetInfo> items, bool silent = false)
|
||||
@ -295,7 +294,7 @@ namespace osu.Game.Beatmaps
|
||||
return beatmapModelManager.Import(archive, lowPriority, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<ILive<BeatmapSetInfo>?> Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
|
||||
public ILive<BeatmapSetInfo>? 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<BeatmapInfo>(importedBeatmap.ID)?.Detach();
|
||||
var refetch = r.Find<BeatmapInfo>(importedBeatmap.ID)?.Detach();
|
||||
|
||||
if (refetch != null)
|
||||
importedBeatmap = refetch;
|
||||
|
@ -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
|
||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||
public BeatmapInfo? QueryBeatmap(Expression<Func<BeatmapInfo, bool>> query)
|
||||
{
|
||||
return ContextFactory.Run(realm => realm.All<BeatmapInfo>().FirstOrDefault(query)?.Detach());
|
||||
return Realm.Run(realm => realm.All<BeatmapInfo>().FirstOrDefault(query)?.Detach());
|
||||
}
|
||||
|
||||
public void Update(BeatmapSetInfo item)
|
||||
{
|
||||
ContextFactory.Write(realm =>
|
||||
Realm.Write(realm =>
|
||||
{
|
||||
var existing = realm.Find<BeatmapSetInfo>(item.ID);
|
||||
item.CopyChangesToRealm(existing);
|
||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// <summary>
|
||||
/// The time signature at this control point.
|
||||
/// </summary>
|
||||
public readonly Bindable<TimeSignatures> TimeSignatureBindable = new Bindable<TimeSignatures>(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple };
|
||||
public readonly Bindable<TimeSignature> TimeSignatureBindable = new Bindable<TimeSignature>(TimeSignature.SimpleQuadruple);
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <summary>
|
||||
/// The time signature at this control point.
|
||||
/// </summary>
|
||||
public TimeSignatures TimeSignature
|
||||
public TimeSignature TimeSignature
|
||||
{
|
||||
get => TimeSignatureBindable.Value;
|
||||
set => TimeSignatureBindable.Value = value;
|
||||
|
@ -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)
|
||||
|
@ -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<HitObject> hitObjects)
|
||||
|
45
osu.Game/Beatmaps/Timing/TimeSignature.cs
Normal file
45
osu.Game/Beatmaps/Timing/TimeSignature.cs
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Beatmaps.Timing
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class TimeSignature : IEquatable<TimeSignature>
|
||||
{
|
||||
/// <summary>
|
||||
/// The numerator of a signature.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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,
|
||||
|
@ -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<byte[]> IStorageResourceProvider.Files => files;
|
||||
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
|
||||
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,13 +27,15 @@ namespace osu.Game.Database
|
||||
{
|
||||
internal class EFToRealmMigrator : CompositeDrawable
|
||||
{
|
||||
public bool FinishedMigrating { get; private set; }
|
||||
public Task<bool> MigrationCompleted => migrationCompleted.Task;
|
||||
|
||||
private readonly TaskCompletionSource<bool> migrationCompleted = new TaskCompletionSource<bool>();
|
||||
|
||||
[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<BeatmapSetInfo>();
|
||||
r.RemoveAll<BeatmapInfo>();
|
||||
r.RemoveAll<BeatmapMetadata>();
|
||||
r.RemoveAll<ScoreInfo>();
|
||||
});
|
||||
|
||||
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<BeatmapSetInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
|
||||
if (realm.All<BeatmapSetInfo>().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<RulesetInfo>(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<RulesetInfo>(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<ScoreInfo>().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<BeatmapInfo>().First(b => b.Hash == score.BeatmapInfo.Hash);
|
||||
var ruleset = realm.Find<RulesetInfo>(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<BeatmapInfo>().First(b => b.Hash == score.BeatmapInfo.Hash);
|
||||
var ruleset = r.Find<RulesetInfo>(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<SkinInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
|
||||
if (!realm.All<SkinInfo>().Any(s => !s.Protected))
|
||||
// note that this cannot be written as: `r.All<SkinInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
|
||||
if (!r.All<SkinInfo>().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<RealmRulesetSetting>().Any())
|
||||
if (!r.All<RealmRulesetSetting>().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,
|
||||
|
46
osu.Game/Database/EmptyRealmSet.cs
Normal file
46
osu.Game/Database/EmptyRealmSet.cs
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<T> : IRealmCollection<T>
|
||||
{
|
||||
private IList<T> emptySet => Array.Empty<T>();
|
||||
|
||||
public IEnumerator<T> 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<T> Freeze() => throw new NotImplementedException();
|
||||
public IDisposable SubscribeForNotifications(NotificationCallbackDelegate<T> 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();
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@ namespace osu.Game.Database
|
||||
/// <param name="archive">An optional archive to use for model population.</param>
|
||||
/// <param name="lowPriority">Whether this is a low priority import.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
Task<ILive<TModel>?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default);
|
||||
ILive<TModel>? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// A user displayable name for the model type associated with this manager.
|
||||
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking context creation during blocking periods.
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||
/// </summary>
|
||||
private readonly SemaphoreSlim contextCreationLock = new SemaphoreSlim(1);
|
||||
private readonly SemaphoreSlim realmRetrievalLock = new SemaphoreSlim(1);
|
||||
|
||||
private readonly ThreadLocal<bool> currentThreadCanCreateContexts = new ThreadLocal<bool>();
|
||||
private readonly ThreadLocal<bool> currentThreadCanCreateRealmInstances = new ThreadLocal<bool>();
|
||||
|
||||
private static readonly GlobalStatistic<int> contexts_created = GlobalStatistics.Get<int>(@"Realm", @"Contexts (Created)");
|
||||
/// <summary>
|
||||
/// Holds a map of functions registered via <see cref="RegisterCustomSubscription"/> and <see cref="RegisterForNotifications{T}"/> 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 <see cref="IDisposable"/> 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 <see cref="IDisposable"/>).
|
||||
/// </summary>
|
||||
private readonly Dictionary<Func<Realm, IDisposable?>, IDisposable?> customSubscriptionsResetMap = new Dictionary<Func<Realm, IDisposable?>, IDisposable?>();
|
||||
|
||||
private readonly object contextLock = new object();
|
||||
/// <summary>
|
||||
/// Holds a map of functions registered via <see cref="RegisterForNotifications{T}"/> 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.
|
||||
/// </summary>
|
||||
private readonly Dictionary<Func<Realm, IDisposable?>, Action> notificationsResetMap = new Dictionary<Func<Realm, IDisposable?>, Action>();
|
||||
|
||||
private Realm? context;
|
||||
private static readonly GlobalStatistic<int> realm_instances_created = GlobalStatistics.Get<int>(@"Realm", @"Instances (Created)");
|
||||
|
||||
public Realm Context
|
||||
private static readonly GlobalStatistic<int> total_subscriptions = GlobalStatistics.Get<int>(@"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<bool> current_thread_subscriptions_allowed = new ThreadLocal<bool>();
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of a realm context factory.
|
||||
/// Construct a new instance.
|
||||
/// </summary>
|
||||
/// <param name="storage">The game storage which will be used to create the realm backing file.</param>
|
||||
/// <param name="filename">The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified.</param>
|
||||
/// <param name="efContextFactory">An EF factory used only for migration purposes.</param>
|
||||
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<ScoreInfo>().Where(s => s.DeletePending);
|
||||
@ -172,34 +203,28 @@ namespace osu.Game.Database
|
||||
/// <summary>
|
||||
/// Run work on realm with a return value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Handles correct context management automatically.
|
||||
/// </remarks>
|
||||
/// <param name="action">The work to run.</param>
|
||||
/// <typeparam name="T">The return type.</typeparam>
|
||||
public T Run<T>(Func<Realm, T> action)
|
||||
{
|
||||
if (ThreadSafety.IsUpdateThread)
|
||||
return action(Context);
|
||||
return action(Realm);
|
||||
|
||||
using (var realm = createContext())
|
||||
using (var realm = getRealmInstance())
|
||||
return action(realm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run work on realm.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Handles correct context management automatically.
|
||||
/// </remarks>
|
||||
/// <param name="action">The work to run.</param>
|
||||
public void Run(Action<Realm> 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
|
||||
/// <summary>
|
||||
/// Write changes to realm.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Handles correct context management and transaction committing automatically.
|
||||
/// </remarks>
|
||||
/// <param name="action">The work to run.</param>
|
||||
public void Write(Action<Realm> 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()
|
||||
/// <summary>
|
||||
/// Subscribe to a realm collection and begin watching for asynchronous changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This adds osu! specific thread and managed state safety checks on top of <see cref="IRealmCollection{T}.SubscribeForNotifications"/>.
|
||||
///
|
||||
/// 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 <c>null</c> <see cref="ChangeSet"/> will be invoked.
|
||||
/// - On recycle end, a standard initial realm callback will arrive, with <c>null</c> <see cref="ChangeSet"/> and an up-to-date collection.
|
||||
/// </remarks>
|
||||
/// <param name="query">The <see cref="IQueryable{T}"/> to observe for changes.</param>
|
||||
/// <typeparam name="T">Type of the elements in the list.</typeparam>
|
||||
/// <param name="callback">The callback to be invoked with the updated <see cref="IRealmCollection{T}"/>.</param>
|
||||
/// <returns>
|
||||
/// A subscription token. It must be kept alive for as long as you want to receive change notifications.
|
||||
/// To stop receiving notifications, call <see cref="IDisposable.Dispose"/>.
|
||||
/// </returns>
|
||||
/// <seealso cref="IRealmCollection{T}.SubscribeForNotifications"/>
|
||||
public IDisposable RegisterForNotifications<T>(Func<Realm, IQueryable<T>> query, NotificationCallbackDelegate<T> callback)
|
||||
where T : RealmObjectBase
|
||||
{
|
||||
if (!ThreadSafety.IsUpdateThread)
|
||||
throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread.");
|
||||
|
||||
lock (realmLock)
|
||||
{
|
||||
Func<Realm, IDisposable?> 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<T>(), null, null));
|
||||
return RegisterCustomSubscription(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run work on realm that will be run every time the update thread realm instance gets recycled.
|
||||
/// </summary>
|
||||
/// <param name="action">The work to run. Return value should be an <see cref="IDisposable"/> from QueryAsyncWithNotifications, or an <see cref="InvokeOnDisposal"/> to clean up any bindings.</param>
|
||||
/// <returns>An <see cref="IDisposable"/> which should be disposed to unsubscribe any inner subscription.</returns>
|
||||
public IDisposable RegisterCustomSubscription(Func<Realm, IDisposable?> 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<Realm, IDisposable?> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush any active contexts and block any further writes.
|
||||
/// Flush any active realm instances and block any further writes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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<RealmContextFactory>(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;
|
||||
}
|
@ -24,17 +24,17 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
private readonly T data;
|
||||
|
||||
private readonly RealmContextFactory realmFactory;
|
||||
private readonly RealmAccess realm;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new instance of live realm data.
|
||||
/// </summary>
|
||||
/// <param name="data">The realm data.</param>
|
||||
/// <param name="realmFactory">The realm factory the data was sourced from. May be null for an unmanaged object.</param>
|
||||
public RealmLive(T data, RealmContextFactory realmFactory)
|
||||
/// <param name="realm">The realm factory the data was sourced from. May be null for an unmanaged object.</param>
|
||||
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)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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<T>(ID);
|
||||
return realm.Realm.Find<T>(ID);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<T>(realmObject);
|
||||
}
|
||||
|
||||
public static List<ILive<T>> ToLive<T>(this IEnumerable<T> realmList, RealmContextFactory realmContextFactory)
|
||||
public static List<ILive<T>> ToLive<T>(this IEnumerable<T> realmList, RealmAccess realm)
|
||||
where T : RealmObject, IHasGuidPrimaryKey
|
||||
{
|
||||
return realmList.Select(l => new RealmLive<T>(l, realmContextFactory)).Cast<ILive<T>>().ToList();
|
||||
return realmList.Select(l => new RealmLive<T>(l, realm)).Cast<ILive<T>>().ToList();
|
||||
}
|
||||
|
||||
public static ILive<T> ToLive<T>(this T realmObject, RealmContextFactory realmContextFactory)
|
||||
public static ILive<T> ToLive<T>(this T realmObject, RealmAccess realm)
|
||||
where T : RealmObject, IHasGuidPrimaryKey
|
||||
{
|
||||
return new RealmLive<T>(realmObject, realmContextFactory);
|
||||
return new RealmLive<T>(realmObject, realm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -272,9 +271,8 @@ namespace osu.Game.Database
|
||||
public static IDisposable? QueryAsyncWithNotifications<T>(this IRealmCollection<T> collection, NotificationCallbackDelegate<T> 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);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.IO
|
||||
/// <summary>
|
||||
/// Access realm.
|
||||
/// </summary>
|
||||
RealmContextFactory RealmContextFactory { get; }
|
||||
RealmAccess RealmAccess { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a texture loader store based on an underlying data store.
|
||||
|
@ -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<RealmKeyBinding> realmKeyBindings;
|
||||
|
||||
[Resolved]
|
||||
private RealmContextFactory realmFactory { get; set; }
|
||||
private RealmAccess realm { get; set; }
|
||||
|
||||
public override IEnumerable<IKeyBinding> 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<RealmKeyBinding>()
|
||||
.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<RealmKeyBinding> queryRealmKeyBindings(Realm realm)
|
||||
{
|
||||
string rulesetName = ruleset?.ShortName;
|
||||
return realm.All<RealmKeyBinding>()
|
||||
.Where(b => b.RulesetName == rulesetName && b.Variant == variant);
|
||||
}
|
||||
|
||||
protected override void ReloadMappings()
|
||||
private void reloadMappings(IQueryable<RealmKeyBinding> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<string> combinations = new List<string>();
|
||||
|
||||
realmFactory.Run(context =>
|
||||
realm.Run(context =>
|
||||
{
|
||||
foreach (var action in context.All<RealmKeyBinding>().Where(b => string.IsNullOrEmpty(b.RulesetName) && (GlobalAction)b.ActionInt == globalAction))
|
||||
{
|
||||
@ -56,21 +56,21 @@ namespace osu.Game.Input
|
||||
/// <param name="rulesets">The rulesets to populate defaults from.</param>
|
||||
public void Register(KeyBindingContainer container, IEnumerable<RulesetInfo> rulesets)
|
||||
{
|
||||
realmFactory.Run(realm =>
|
||||
realm.Run(r =>
|
||||
{
|
||||
using (var transaction = realm.BeginWrite())
|
||||
using (var transaction = r.BeginWrite())
|
||||
{
|
||||
// intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed.
|
||||
// this is much faster as a result.
|
||||
var existingBindings = realm.All<RealmKeyBinding>().ToList();
|
||||
var existingBindings = r.All<RealmKeyBinding>().ToList();
|
||||
|
||||
insertDefaults(realm, existingBindings, container.DefaultKeyBindings);
|
||||
insertDefaults(r, existingBindings, container.DefaultKeyBindings);
|
||||
|
||||
foreach (var ruleset in rulesets)
|
||||
{
|
||||
var instance = ruleset.CreateInstance();
|
||||
foreach (int variant in instance.AvailableVariants)
|
||||
insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant);
|
||||
insertDefaults(r, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant);
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Online
|
||||
private IDisposable? realmSubscription;
|
||||
|
||||
[Resolved]
|
||||
private RealmContextFactory realmContextFactory { get; set; } = null!;
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
public BeatmapDownloadTracker(IBeatmapSetInfo trackedItem)
|
||||
: base(trackedItem)
|
||||
@ -42,7 +42,7 @@ namespace osu.Game.Online
|
||||
// Used to interact with manager classes that don't support interface types. Will eventually be replaced.
|
||||
var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID };
|
||||
|
||||
realmSubscription = realmContextFactory.Context.All<BeatmapSetInfo>().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) =>
|
||||
realmSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) =>
|
||||
{
|
||||
if (items.Any())
|
||||
Schedule(() => UpdateState(DownloadState.LocallyAvailable));
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Online.Rooms
|
||||
protected override bool RequiresChildrenUpdate => true;
|
||||
|
||||
[Resolved]
|
||||
private RealmContextFactory realmContextFactory { get; set; } = null!;
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The availability state of the currently selected playlist item.
|
||||
@ -78,7 +78,7 @@ namespace osu.Game.Online.Rooms
|
||||
|
||||
// handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow).
|
||||
realmSubscription?.Dispose();
|
||||
realmSubscription = filteredBeatmaps().QueryAsyncWithNotifications((items, changes, ___) =>
|
||||
realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(), (items, changes, ___) =>
|
||||
{
|
||||
if (changes == null)
|
||||
return;
|
||||
@ -128,9 +128,9 @@ namespace osu.Game.Online.Rooms
|
||||
int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID;
|
||||
string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash;
|
||||
|
||||
return realmContextFactory.Context
|
||||
.All<BeatmapInfo>()
|
||||
.Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum);
|
||||
return realm.Realm
|
||||
.All<BeatmapInfo>()
|
||||
.Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Online
|
||||
private IDisposable? realmSubscription;
|
||||
|
||||
[Resolved]
|
||||
private RealmContextFactory realmContextFactory { get; set; } = null!;
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
public ScoreDownloadTracker(ScoreInfo trackedItem)
|
||||
: base(trackedItem)
|
||||
@ -47,7 +47,7 @@ namespace osu.Game.Online
|
||||
Downloader.DownloadBegan += downloadBegan;
|
||||
Downloader.DownloadFailed += downloadFailed;
|
||||
|
||||
realmSubscription = realmContextFactory.Context.All<ScoreInfo>().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) =>
|
||||
realmSubscription = realm.RegisterForNotifications(r => r.All<ScoreInfo>().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) =>
|
||||
{
|
||||
if (items.Any())
|
||||
Schedule(() => UpdateState(DownloadState.LocallyAvailable));
|
||||
|
@ -149,7 +149,7 @@ namespace osu.Game
|
||||
|
||||
private MultiplayerClient multiplayerClient;
|
||||
|
||||
private RealmContextFactory realmFactory;
|
||||
private RealmAccess realm;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
@ -192,9 +192,9 @@ namespace osu.Game
|
||||
if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME))
|
||||
dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage));
|
||||
|
||||
dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", EFContextFactory));
|
||||
dependencies.Cache(realm = new RealmAccess(Storage, "client", EFContextFactory));
|
||||
|
||||
dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage));
|
||||
dependencies.Cache(RulesetStore = new RulesetStore(realm, Storage));
|
||||
dependencies.CacheAs<IRulesetStore>(RulesetStore);
|
||||
|
||||
// Backup is taken here rather than in EFToRealmMigrator to avoid recycling realm contexts
|
||||
@ -205,11 +205,16 @@ namespace osu.Game
|
||||
string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
|
||||
|
||||
EFContextFactory.CreateBackup($"client.{migration}.db");
|
||||
realmFactory.CreateBackup($"client.{migration}.realm");
|
||||
realm.CreateBackup($"client.{migration}.realm");
|
||||
|
||||
using (var source = Storage.GetStream("collection.db"))
|
||||
using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew))
|
||||
source.CopyTo(destination);
|
||||
{
|
||||
if (source != null)
|
||||
{
|
||||
using (var destination = Storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew))
|
||||
source.CopyTo(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies.CacheAs(Storage);
|
||||
@ -225,7 +230,7 @@ namespace osu.Game
|
||||
|
||||
Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY;
|
||||
|
||||
dependencies.Cache(SkinManager = new SkinManager(Storage, realmFactory, Host, Resources, Audio, Scheduler));
|
||||
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
|
||||
dependencies.CacheAs<ISkinSource>(SkinManager);
|
||||
|
||||
EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
|
||||
@ -240,8 +245,8 @@ namespace osu.Game
|
||||
var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);
|
||||
|
||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realmFactory, Scheduler, Host, () => difficultyCache, LocalConfig));
|
||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realmFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true));
|
||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, Host, () => difficultyCache, LocalConfig));
|
||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true));
|
||||
|
||||
dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API));
|
||||
dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API));
|
||||
@ -259,7 +264,7 @@ namespace osu.Game
|
||||
dependencies.Cache(scorePerformanceManager);
|
||||
AddInternal(scorePerformanceManager);
|
||||
|
||||
dependencies.CacheAs<IRulesetConfigCache>(rulesetConfigCache = new RulesetConfigCache(realmFactory, RulesetStore));
|
||||
dependencies.CacheAs<IRulesetConfigCache>(rulesetConfigCache = new RulesetConfigCache(realm, RulesetStore));
|
||||
|
||||
var powerStatus = CreateBatteryInfo();
|
||||
if (powerStatus != null)
|
||||
@ -303,7 +308,7 @@ namespace osu.Game
|
||||
|
||||
base.Content.Add(CreateScalingContainer().WithChildren(mainContent));
|
||||
|
||||
KeyBindingStore = new RealmKeyBindingStore(realmFactory, keyCombinationProvider);
|
||||
KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider);
|
||||
KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets);
|
||||
|
||||
dependencies.Cache(globalBindings);
|
||||
@ -405,7 +410,7 @@ namespace osu.Game
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
realmBlocker = realmFactory.BlockAllOperations();
|
||||
realmBlocker = realm.BlockAllOperations();
|
||||
|
||||
readyToRun.Set();
|
||||
}, false);
|
||||
@ -483,7 +488,7 @@ namespace osu.Game
|
||||
BeatmapManager?.Dispose();
|
||||
LocalConfig?.Dispose();
|
||||
|
||||
realmFactory?.Dispose();
|
||||
realm?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,16 +30,7 @@ namespace osu.Game.Overlays
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
public IBindableList<BeatmapSetInfo> BeatmapSets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LoadState < LoadState.Ready)
|
||||
throw new InvalidOperationException($"{nameof(BeatmapSets)} should not be accessed before the music controller is loaded.");
|
||||
|
||||
return beatmapSets;
|
||||
}
|
||||
}
|
||||
public IBindableList<BeatmapSetInfo> BeatmapSets => beatmapSets;
|
||||
|
||||
/// <summary>
|
||||
/// Point in time after which the current track will be restarted on triggering a "previous track" action.
|
||||
@ -69,7 +60,7 @@ namespace osu.Game.Overlays
|
||||
public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000));
|
||||
|
||||
[Resolved]
|
||||
private RealmContextFactory realmFactory { get; set; }
|
||||
private RealmAccess realm { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -80,26 +71,26 @@ namespace osu.Game.Overlays
|
||||
mods.BindValueChanged(_ => ResetTrackAdjustments(), true);
|
||||
}
|
||||
|
||||
private IQueryable<BeatmapSetInfo> queryRealmBeatmapSets() =>
|
||||
realm.Realm
|
||||
.All<BeatmapSetInfo>()
|
||||
.Where(s => !s.DeletePending);
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
var availableBeatmaps = realmFactory.Context
|
||||
.All<BeatmapSetInfo>()
|
||||
.Where(s => !s.DeletePending);
|
||||
|
||||
// ensure we're ready before completing async load.
|
||||
// probably not a good way of handling this (as there is a period we aren't watching for changes until the realm subscription finishes up.
|
||||
foreach (var s in availableBeatmaps)
|
||||
beatmapSets.Add(s);
|
||||
|
||||
beatmapSubscription = availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged);
|
||||
beatmapSubscription = realm.RegisterForNotifications(r => queryRealmBeatmapSets(), beatmapsChanged);
|
||||
}
|
||||
|
||||
private void beatmapsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet changes, Exception error)
|
||||
{
|
||||
if (changes == null)
|
||||
{
|
||||
beatmapSets.Clear();
|
||||
foreach (var s in sender)
|
||||
beatmapSets.Add(s.Detach());
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (int i in changes.InsertedIndices)
|
||||
beatmapSets.Insert(i, sender[i].Detach());
|
||||
|
@ -12,6 +12,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Localisation;
|
||||
@ -118,6 +119,8 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
++runningDepth;
|
||||
|
||||
Logger.Log($"⚠️ {notification.Text}");
|
||||
|
||||
notification.Closed += notificationClosed;
|
||||
|
||||
if (notification is IHasCompletionTarget hasCompletionTarget)
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osuTK;
|
||||
@ -25,6 +26,8 @@ namespace osu.Game.Overlays.Notifications
|
||||
/// </summary>
|
||||
public event Action Closed;
|
||||
|
||||
public abstract LocalisableString Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this notification should forcefully display itself.
|
||||
/// </summary>
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Notifications
|
||||
|
||||
private LocalisableString text;
|
||||
|
||||
public LocalisableString Text
|
||||
public override LocalisableString Text
|
||||
{
|
||||
get => text;
|
||||
set
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Notifications
|
||||
{
|
||||
private LocalisableString text;
|
||||
|
||||
public LocalisableString Text
|
||||
public override LocalisableString Text
|
||||
{
|
||||
get => text;
|
||||
set
|
||||
|
@ -1,9 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Localisation;
|
||||
@ -15,8 +19,11 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
|
||||
protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, RealmContextFactory realmFactory)
|
||||
private void load(GameHost host, RealmAccess realm)
|
||||
{
|
||||
SettingsButton blockAction;
|
||||
SettingsButton unblockAction;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsButton
|
||||
@ -30,11 +37,56 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
|
||||
Action = () =>
|
||||
{
|
||||
// Blocking operations implicitly causes a Compact().
|
||||
using (realmFactory.BlockAllOperations())
|
||||
using (realm.BlockAllOperations())
|
||||
{
|
||||
}
|
||||
}
|
||||
},
|
||||
blockAction = new SettingsButton
|
||||
{
|
||||
Text = "Block realm",
|
||||
},
|
||||
unblockAction = new SettingsButton
|
||||
{
|
||||
Text = "Unblock realm",
|
||||
},
|
||||
};
|
||||
|
||||
blockAction.Action = () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = realm.BlockAllOperations();
|
||||
|
||||
blockAction.Enabled.Value = false;
|
||||
|
||||
// As a safety measure, unblock after 10 seconds.
|
||||
// This is to handle the case where a dev may block, but then something on the update thread
|
||||
// accesses realm and blocks for eternity.
|
||||
Task.Factory.StartNew(() =>
|
||||
{
|
||||
Thread.Sleep(10000);
|
||||
unblock();
|
||||
});
|
||||
|
||||
unblockAction.Action = unblock;
|
||||
|
||||
void unblock()
|
||||
{
|
||||
token?.Dispose();
|
||||
token = null;
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
blockAction.Enabled.Value = true;
|
||||
unblockAction.Action = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, "Blocking realm failed");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private RealmContextFactory realmFactory { get; set; }
|
||||
private RealmAccess realm { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
@ -386,10 +386,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
|
||||
private void updateStoreFromButton(KeyButton button)
|
||||
{
|
||||
realmFactory.Run(realm =>
|
||||
realm.Run(r =>
|
||||
{
|
||||
var binding = realm.Find<RealmKeyBinding>(((IHasGuidPrimaryKey)button.KeyBinding).ID);
|
||||
realm.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString);
|
||||
var binding = r.Find<RealmKeyBinding>(((IHasGuidPrimaryKey)button.KeyBinding).ID);
|
||||
r.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -30,13 +30,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RealmContextFactory realmFactory)
|
||||
private void load(RealmAccess realm)
|
||||
{
|
||||
string rulesetName = Ruleset?.ShortName;
|
||||
|
||||
var bindings = realmFactory.Run(realm => realm.All<RealmKeyBinding>()
|
||||
.Where(b => b.RulesetName == rulesetName && b.Variant == variant)
|
||||
.Detach());
|
||||
var bindings = realm.Run(r => r.All<RealmKeyBinding>()
|
||||
.Where(b => b.RulesetName == rulesetName && b.Variant == variant)
|
||||
.Detach());
|
||||
|
||||
foreach (var defaultGroup in Defaults.GroupBy(d => d.Action))
|
||||
{
|
||||
|
@ -47,10 +47,15 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
private SkinManager skins { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private RealmContextFactory realmFactory { get; set; }
|
||||
private RealmAccess realm { get; set; }
|
||||
|
||||
private IDisposable realmSubscription;
|
||||
private IQueryable<SkinInfo> realmSkins;
|
||||
|
||||
private IQueryable<SkinInfo> queryRealmSkins() =>
|
||||
realm.Realm.All<SkinInfo>()
|
||||
.Where(s => !s.DeletePending)
|
||||
.OrderByDescending(s => s.Protected) // protected skins should be at the top.
|
||||
.ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
[BackgroundDependencyLoader(permitNulls: true)]
|
||||
private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor)
|
||||
@ -78,20 +83,12 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
|
||||
skinDropdown.Current = dropdownBindable;
|
||||
|
||||
realmSkins = realmFactory.Context.All<SkinInfo>()
|
||||
.Where(s => !s.DeletePending)
|
||||
.OrderByDescending(s => s.Protected) // protected skins should be at the top.
|
||||
.ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
realmSubscription = realmSkins
|
||||
.QueryAsyncWithNotifications((sender, changes, error) =>
|
||||
{
|
||||
if (changes == null)
|
||||
return;
|
||||
|
||||
// Eventually this should be handling the individual changes rather than refreshing the whole dropdown.
|
||||
updateItems();
|
||||
});
|
||||
realmSubscription = realm.RegisterForNotifications(r => queryRealmSkins(), (sender, changes, error) =>
|
||||
{
|
||||
// The first fire of this is a bit redundant due to the call below,
|
||||
// but this is safest in case the subscription is restored after a context recycle.
|
||||
updateItems();
|
||||
});
|
||||
|
||||
updateItems();
|
||||
|
||||
@ -131,9 +128,9 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
|
||||
private void updateItems()
|
||||
{
|
||||
int protectedCount = realmSkins.Count(s => s.Protected);
|
||||
int protectedCount = queryRealmSkins().Count(s => s.Protected);
|
||||
|
||||
skinItems = realmSkins.ToLive(realmFactory);
|
||||
skinItems = queryRealmSkins().ToLive(realm);
|
||||
|
||||
skinItems.Insert(protectedCount, random_skin_info);
|
||||
|
||||
|
@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
protected FillFlowContainer Flow;
|
||||
|
||||
[Resolved]
|
||||
private RealmContextFactory realmFactory { get; set; }
|
||||
private RealmAccess realm { get; set; }
|
||||
|
||||
protected ToolbarButton()
|
||||
: base(HoverSampleSet.Toolbar)
|
||||
@ -207,7 +207,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
{
|
||||
if (Hotkey == null) return;
|
||||
|
||||
var realmKeyBinding = realmFactory.Context.All<RealmKeyBinding>().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value);
|
||||
var realmKeyBinding = realm.Realm.All<RealmKeyBinding>().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value);
|
||||
|
||||
if (realmKeyBinding != null)
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Configuration
|
||||
public abstract class RulesetConfigManager<TLookup> : ConfigManager<TLookup>, IRulesetConfigManager
|
||||
where TLookup : struct, Enum
|
||||
{
|
||||
private readonly RealmContextFactory realmFactory;
|
||||
private readonly RealmAccess realm;
|
||||
|
||||
private readonly int variant;
|
||||
|
||||
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Configuration
|
||||
|
||||
protected RulesetConfigManager(SettingsStore store, RulesetInfo ruleset, int? variant = null)
|
||||
{
|
||||
realmFactory = store?.Realm;
|
||||
realm = store?.Realm;
|
||||
|
||||
rulesetName = ruleset.ShortName;
|
||||
|
||||
@ -37,10 +37,10 @@ namespace osu.Game.Rulesets.Configuration
|
||||
|
||||
protected override void PerformLoad()
|
||||
{
|
||||
if (realmFactory != null)
|
||||
if (realm != null)
|
||||
{
|
||||
// As long as RulesetConfigCache exists, there is no need to subscribe to realm events.
|
||||
databasedSettings = realmFactory.Context.All<RealmRulesetSetting>().Where(b => b.RulesetName == rulesetName && b.Variant == variant).ToList();
|
||||
databasedSettings = realm.Realm.All<RealmRulesetSetting>().Where(b => b.RulesetName == rulesetName && b.Variant == variant).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,11 +56,11 @@ namespace osu.Game.Rulesets.Configuration
|
||||
pendingWrites.Clear();
|
||||
}
|
||||
|
||||
realmFactory?.Write(realm =>
|
||||
realm?.Write(r =>
|
||||
{
|
||||
foreach (var c in changed)
|
||||
{
|
||||
var setting = realm.All<RealmRulesetSetting>().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString());
|
||||
var setting = r.All<RealmRulesetSetting>().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString());
|
||||
|
||||
setting.Value = ConfigStore[c].ToString();
|
||||
}
|
||||
@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Configuration
|
||||
Variant = variant,
|
||||
};
|
||||
|
||||
realmFactory?.Context.Write(() => realmFactory.Context.Add(setting));
|
||||
realm?.Realm.Write(() => realm.Realm.Add(setting));
|
||||
|
||||
databasedSettings.Add(setting);
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
if (!IsBeatSyncedWithTrack) return;
|
||||
|
||||
int timeSignature = (int)timingPoint.TimeSignature;
|
||||
int timeSignature = timingPoint.TimeSignature.Numerator;
|
||||
|
||||
// play metronome from one measure before the first object.
|
||||
if (BeatSyncClock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user