From 2a59735525abb6d481fadc044808f497ace7f2a4 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sat, 15 Jan 2022 21:43:28 +0100 Subject: [PATCH 01/85] Initial commit --- .../Mods/CatchModFlashlight.cs | 21 +++------- .../Mods/ManiaModFlashlight.cs | 13 ++++-- .../Mods/OsuModFlashlight.cs | 42 ++++++++++--------- .../Mods/TaikoModFlashlight.cs | 20 ++++----- osu.Game/Rulesets/Mods/ModFlashlight.cs | 42 +++++++++++++++++++ 5 files changed, 89 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index f399f48ebd..e5da168dc6 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -15,9 +15,10 @@ namespace osu.Game.Rulesets.Catch.Mods { public override double ScoreMultiplier => 1.12; - private const float default_flashlight_size = 350; + public override bool DefaultComboDependency => true; + public override float DefaultRadius => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); private CatchPlayfield playfield; @@ -31,10 +32,10 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) : base(isRadiusBasedOnCombo, initialRadius) { this.playfield = playfield; - FlashlightSize = new Vector2(0, getSizeFor(0)); + FlashlightSize = new Vector2(0, GetRadiusFor(0)); } protected override void Update() @@ -44,19 +45,9 @@ namespace osu.Game.Rulesets.Catch.Mods FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this); } - private float getSizeFor(int combo) - { - if (combo > 200) - return default_flashlight_size * 0.8f; - else if (combo > 100) - return default_flashlight_size * 0.9f; - else - return default_flashlight_size; - } - protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 86a00271e9..676b5f3842 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -16,17 +16,19 @@ 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; + public override bool DefaultComboDependency => false; + public override float DefaultRadius => 180; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight() + public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius) + : base(isRadiusBasedOnCombo, initialRadius) { - FlashlightSize = new Vector2(0, default_flashlight_size); + FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); AddLayout(flashlightProperties); } @@ -46,9 +48,12 @@ namespace osu.Game.Rulesets.Mania.Mods protected override void OnComboChange(ValueChangedEvent e) { + this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "RectangularFlashlight"; } } } + + diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 300a9d48aa..f15527460c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods @@ -21,13 +20,16 @@ namespace osu.Game.Rulesets.Osu.Mods { public override double ScoreMultiplier => 1.12; - private const float default_flashlight_size = 180; + public override bool DefaultComboDependency => true; + + //private const float default_flashlight_size = 180; + public override float DefaultRadius => 180; private const double default_follow_delay = 120; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -35,13 +37,6 @@ namespace osu.Game.Rulesets.Osu.Mods s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; } - public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - base.ApplyToDrawableRuleset(drawableRuleset); - - flashlight.FollowDelay = FollowDelay.Value; - } - [SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")] public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay) { @@ -54,9 +49,15 @@ namespace osu.Game.Rulesets.Osu.Mods { public double FollowDelay { private get; set; } - public OsuFlashlight() + //public float InitialRadius { private get; set; } + public bool ChangeRadius { private get; set; } + + public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay) + : base(isRadiusBasedOnCombo, initialRadius) { - FlashlightSize = new Vector2(0, getSizeFor(0)); + FollowDelay = followDelay; + + FlashlightSize = new Vector2(0, GetRadiusFor(0)); } public void OnSliderTrackingChange(ValueChangedEvent e) @@ -78,17 +79,20 @@ namespace osu.Game.Rulesets.Osu.Mods 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; + if (ChangeRadius) + { + if (combo > 200) + return InitialRadius * 0.8f; + else if (combo > 100) + return InitialRadius * 0.9f; + } + + return InitialRadius; } protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 0a325f174e..29f29863c0 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -16,9 +16,12 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => 1.12; - private const float default_flashlight_size = 250; + public override bool DefaultComboDependency => true; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield); + //private const float default_flashlight_size = 250; + public override float DefaultRadius => 250; + + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); private TaikoPlayfield playfield; @@ -33,7 +36,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(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius) + : base(isRadiusBasedOnCombo, initialRadius) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); @@ -43,15 +47,9 @@ 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, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + return new Vector2(0, GetRadiusFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); } protected override void OnComboChange(ValueChangedEvent e) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index a77a83b36c..bff0e2f12d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.Timing; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.OpenGL.Vertices; using osu.Game.Rulesets.Objects; @@ -32,8 +33,26 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public Bindable ChangeRadius { get; private set; } + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public BindableNumber InitialRadius { get; private set; } + + public abstract float DefaultRadius { get; } + + public abstract bool DefaultComboDependency { get; } + internal ModFlashlight() { + InitialRadius = new BindableFloat + { + MinValue = 90f, + MaxValue = 250f, + Precision = 5f, + }; + + ChangeRadius = new BindableBool(DefaultComboDependency); } } @@ -93,6 +112,16 @@ namespace osu.Game.Rulesets.Mods public List Breaks; + public readonly bool IsRadiusBasedOnCombo; + + public readonly float InitialRadius; + + protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius) + { + IsRadiusBasedOnCombo = isRadiusBasedOnCombo; + InitialRadius = initialRadius; + } + [BackgroundDependencyLoader] private void load(ShaderManager shaderManager) { @@ -124,6 +153,19 @@ namespace osu.Game.Rulesets.Mods protected abstract string FragmentShader { get; } + protected float GetRadiusFor(int combo) + { + if (IsRadiusBasedOnCombo) + { + if (combo > 200) + return InitialRadius * 0.8f; + else if (combo > 100) + return InitialRadius * 0.9f; + } + + return InitialRadius; + } + private Vector2 flashlightPosition; protected Vector2 FlashlightPosition From 57cc2f78933fb89c3226c7ab28bc1a3fe6c0e8da Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sun, 16 Jan 2022 14:26:26 +0100 Subject: [PATCH 02/85] Adjustment to size values of FL per mode --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 3 ++- osu.Game/Rulesets/Mods/ModFlashlight.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e5da168dc6..9d9fa5aed4 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) : base(isRadiusBasedOnCombo, initialRadius) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) + : base(isRadiusBasedOnCombo, initialRadius) { this.playfield = playfield; FlashlightSize = new Vector2(0, GetRadiusFor(0)); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index bff0e2f12d..c218ab45fe 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -45,10 +45,10 @@ namespace osu.Game.Rulesets.Mods internal ModFlashlight() { - InitialRadius = new BindableFloat + InitialRadius = new BindableFloat(DefaultRadius) { - MinValue = 90f, - MaxValue = 250f, + MinValue = DefaultRadius * .5f, + MaxValue = DefaultRadius * 1.5f, Precision = 5f, }; From bd308ca38c5afbddbc18cebaa4e8effa1d4cca90 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 17 Jan 2022 15:15:25 +0100 Subject: [PATCH 03/85] Cleanup --- .../Mods/ManiaModFlashlight.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 15 ++------------- .../Mods/TaikoModFlashlight.cs | 1 - 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 676b5f3842..4fff736c57 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -55,5 +55,3 @@ namespace osu.Game.Rulesets.Mania.Mods } } } - - diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index f15527460c..f381d14ffe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.Mods private const double default_follow_delay = 120; + + private OsuFlashlight flashlight; public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value); @@ -77,19 +79,6 @@ namespace osu.Game.Rulesets.Osu.Mods return base.OnMouseMove(e); } - private float getSizeFor(int combo) - { - if (ChangeRadius) - { - if (combo > 200) - return InitialRadius * 0.8f; - else if (combo > 100) - return InitialRadius * 0.9f; - } - - return InitialRadius; - } - protected override void OnComboChange(ValueChangedEvent e) { this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 29f29863c0..76f7c45b75 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -48,7 +48,6 @@ namespace osu.Game.Rulesets.Taiko.Mods private Vector2 getSizeFor(int combo) { // Preserve flashlight size through the playfield's aspect adjustment. - // return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); return new Vector2(0, GetRadiusFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); } From 2006620a2c02d60c83792803b4276cb163a3b4a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 17:55:27 +0900 Subject: [PATCH 04/85] Fix `IntroScreen` retrieving and iterating all realm beatmap sets --- osu.Game/Screens/Menu/IntroScreen.cs | 82 ++++++++++++++-------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index d98cb8056f..db9d9b83c4 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; +using Realms; namespace osu.Game.Screens.Menu { @@ -93,58 +94,57 @@ namespace osu.Game.Screens.Menu MenuMusic = config.GetBindable(OsuSetting.MenuMusic); seeya = audio.Samples.Get(SeeyaSampleName); - ILive setInfo = null; - // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) { - var sets = beatmaps.GetAllUsableBeatmapSets(); - - if (sets.Count > 0) + realmContextFactory.Run(realm => { - setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); - setInfo?.PerformRead(s => + var sets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + + int setCount = sets.Count; + + if (sets.Any()) + { + var found = sets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); + + if (found != null) + initialBeatmap = beatmaps.GetWorkingBeatmap(found); + } + }); + + // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. + if (initialBeatmap == null) + { + if (!loadThemedIntro()) + { + // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. + // this could happen if a user has nuked their files store. for now, reimport to repair this. + var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); + + import?.PerformWrite(b => b.Protected = true); + + loadThemedIntro(); + } + } + + bool loadThemedIntro() + { + var setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + + if (setInfo == null) + return false; + + setInfo.PerformRead(s => { if (s.Beatmaps.Count == 0) return; - initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]); + initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); }); + + return UsingThemedIntro = initialBeatmap != null; } } - - // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. - if (setInfo == null) - { - if (!loadThemedIntro()) - { - // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. - // this could happen if a user has nuked their files store. for now, reimport to repair this. - var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); - - import?.PerformWrite(b => b.Protected = true); - - loadThemedIntro(); - } - } - - bool loadThemedIntro() - { - setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); - - if (setInfo == null) - return false; - - setInfo.PerformRead(s => - { - if (s.Beatmaps.Count == 0) - return; - - initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); - }); - - return UsingThemedIntro = initialBeatmap != null; - } } public override void OnResuming(IScreen last) From 18bf690a30bc9d5c5c6d53133469fc462bcf62ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 18:13:21 +0900 Subject: [PATCH 05/85] Add `Register` method for subscription purposes and update safeties --- osu.Game/Database/RealmContextFactory.cs | 28 ++++++++++++++++++++++ osu.Game/Database/RealmObjectExtensions.cs | 6 ++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 888ffb1dd5..169333bff8 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -88,6 +88,10 @@ namespace osu.Game.Database } } + internal static bool CurrentThreadSubscriptionsAllowed => current_thread_subscriptions_allowed.Value; + + private static readonly ThreadLocal current_thread_subscriptions_allowed = new ThreadLocal(); + /// /// Construct a new instance of a realm context factory. /// @@ -222,6 +226,30 @@ namespace osu.Game.Database } } + /// + /// Run work on realm that will be run every time the update thread realm context gets recycled. + /// + /// The work to run. + public void Register(Action action) + { + if (!ThreadSafety.IsUpdateThread && context != null) + throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + + if (ThreadSafety.IsUpdateThread) + { + current_thread_subscriptions_allowed.Value = true; + action(Context); + current_thread_subscriptions_allowed.Value = false; + } + else + { + current_thread_subscriptions_allowed.Value = true; + using (var realm = createContext()) + action(realm); + current_thread_subscriptions_allowed.Value = false; + } + } + private Realm createContext() { if (isDisposed) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index c25aeab336..c30e1699b9 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Runtime.Serialization; using AutoMapper; using AutoMapper.Internal; -using osu.Framework.Development; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Models; @@ -272,9 +271,8 @@ namespace osu.Game.Database public static IDisposable? QueryAsyncWithNotifications(this IRealmCollection collection, NotificationCallbackDelegate callback) where T : RealmObjectBase { - // Subscriptions can only work on the main thread. - if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException("Cannot subscribe for realm notifications from a non-update thread."); + if (!RealmContextFactory.CurrentThreadSubscriptionsAllowed) + throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.Register)}"); return collection.SubscribeForNotifications(callback); } From 45aea9add5209953d7e76a093a74f5ad91732a60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 18:50:25 +0900 Subject: [PATCH 06/85] Implement full subscription flow --- osu.Game/Database/RealmContextFactory.cs | 46 +++++++++++++++++------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 169333bff8..62717eb880 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -80,6 +82,10 @@ namespace osu.Game.Database { context = createContext(); Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); + + // Resubscribe any subscriptions + foreach (var action in subscriptionActions.Keys) + registerSubscription(action); } // creating a context will ensure our schema is up-to-date and migrated. @@ -226,26 +232,42 @@ namespace osu.Game.Database } } + private readonly Dictionary, IDisposable?> subscriptionActions = new Dictionary, IDisposable?>(); + /// /// Run work on realm that will be run every time the update thread realm context gets recycled. /// - /// The work to run. - public void Register(Action action) + /// The work to run. Return value should be an from QueryAsyncWithNotifications, or an to clean up any bindings. + /// An which should be disposed to unsubscribe any inner subscription. + public IDisposable Register(Func action) { - if (!ThreadSafety.IsUpdateThread && context != null) - throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); - if (ThreadSafety.IsUpdateThread) + subscriptionActions.Add(action, null); + registerSubscription(action); + + return new InvokeOnDisposal(() => { - current_thread_subscriptions_allowed.Value = true; - action(Context); - current_thread_subscriptions_allowed.Value = false; - } - else + // TODO: this likely needs to be run on the update thread. + if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + { + unsubscriptionAction?.Dispose(); + subscriptionActions.Remove(action); + } + }); + } + + private void registerSubscription(Func action) + { + Debug.Assert(ThreadSafety.IsUpdateThread); + + lock (contextLock) { + Debug.Assert(context != null); + current_thread_subscriptions_allowed.Value = true; - using (var realm = createContext()) - action(realm); + subscriptionActions[action] = action(context); current_thread_subscriptions_allowed.Value = false; } } From 1f157d729dbc0d1bc2cdbed374b41bbef650e121 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 19:39:49 +0900 Subject: [PATCH 07/85] Update existing subscriptions to new style Fix missing detach calls in `MusicController` --- osu.Game.Tests/Database/GeneralUsageTests.cs | 3 ++- osu.Game.Tests/Database/RealmLiveTests.cs | 4 ++- osu.Game/Database/RealmContextFactory.cs | 5 +--- .../Bindings/DatabasedKeyBindingContainer.cs | 26 +++++++++--------- osu.Game/Online/BeatmapDownloadTracker.cs | 4 +-- .../OnlinePlayBeatmapAvailabilityTracker.cs | 4 +-- osu.Game/Online/ScoreDownloadTracker.cs | 4 +-- osu.Game/Overlays/MusicController.cs | 17 +++++++----- .../Sections/DebugSettings/MemorySettings.cs | 4 +++ .../Overlays/Settings/Sections/SkinSection.cs | 22 +++++++-------- osu.Game/Screens/Select/BeatmapCarousel.cs | 17 ++++++------ .../Screens/Select/Carousel/TopLocalRank.cs | 25 ++++++++--------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 27 ++++++++++--------- osu.Game/Screens/Spectate/SpectatorScreen.cs | 20 +++++++------- 14 files changed, 98 insertions(+), 84 deletions(-) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 9ebe94b383..c82c1b6e59 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Database { bool callbackRan = false; - realmFactory.Run(realm => + realmFactory.Register(realm => { var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) => { @@ -60,6 +60,7 @@ namespace osu.Game.Tests.Database realmFactory.Run(r => r.Refresh()); subscription?.Dispose(); + return null; }); Assert.IsTrue(callbackRan); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 2f16df4624..5549c140f6 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Database { int changesTriggered = 0; - realmFactory.Run(outerRealm => + realmFactory.Register(outerRealm => { outerRealm.All().QueryAsyncWithNotifications(gotChange); ILive? liveBeatmap = null; @@ -277,6 +277,8 @@ namespace osu.Game.Tests.Database r.Remove(resolved); }); }); + + return null; }); void gotChange(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 62717eb880..c11ff61039 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -244,7 +244,6 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); - subscriptionActions.Add(action, null); registerSubscription(action); return new InvokeOnDisposal(() => @@ -264,10 +263,8 @@ namespace osu.Game.Database lock (contextLock) { - Debug.Assert(context != null); - current_thread_subscriptions_allowed.Value = true; - subscriptionActions[action] = action(context); + subscriptionActions[action] = action(Context); current_thread_subscriptions_allowed.Value = false; } } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 03b069d431..5a9797ffce 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -23,7 +23,6 @@ namespace osu.Game.Input.Bindings private readonly int? variant; private IDisposable realmSubscription; - private IQueryable realmKeyBindings; [Resolved] private RealmContextFactory realmFactory { get; set; } @@ -47,22 +46,25 @@ namespace osu.Game.Input.Bindings throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); } + private IQueryable realmKeyBindings + { + get + { + string rulesetName = ruleset?.ShortName; + return realmFactory.Context.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); + } + } + protected override void LoadComplete() { - string rulesetName = ruleset?.ShortName; - - realmKeyBindings = realmFactory.Context.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); - - realmSubscription = realmKeyBindings + realmSubscription = realmFactory.Register(realm => realmKeyBindings .QueryAsyncWithNotifications((sender, changes, error) => { - // first subscription ignored as we are handling this in LoadComplete. - if (changes == null) - return; - + // 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(); - }); + })); base.LoadComplete(); } diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index be5bdea6f1..d50ab5a347 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -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().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -54,7 +54,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(beatmapSetInfo)); }); } - }); + })); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 1f77b1d383..fdcf2b39c6 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -78,13 +78,13 @@ 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 = realmContextFactory.Register(realm => filteredBeatmaps().QueryAsyncWithNotifications((items, changes, ___) => { if (changes == null) return; Scheduler.AddOnce(updateAvailability); - }); + })); }, true); } diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index b34586567d..de1d6fd94a 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.Context.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -59,7 +59,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(scoreInfo)); }); } - }); + })); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 70f8332295..01a2c9d354 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -80,26 +80,31 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } + private IQueryable availableBeatmaps => realmFactory.Context + .All() + .Where(s => !s.DeletePending); + protected override void LoadComplete() { base.LoadComplete(); - var availableBeatmaps = realmFactory.Context - .All() - .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); + beatmapSets.Add(s.Detach()); - beatmapSubscription = availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged); + beatmapSubscription = realmFactory.Register(realm => availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged)); } private void beatmapsChanged(IRealmCollection 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()); diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 8d4fc5fc9f..84a54b208c 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -33,6 +33,10 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings using (realmFactory.BlockAllOperations()) { } + + // retrieve context to revive realm subscriptions. + // TODO: should we do this from OsuGame or RealmContextFactory or something? Answer: yes. + var _ = realmFactory.Context; } }, }; diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 0fa6d78d4b..11a7275168 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -50,7 +50,12 @@ namespace osu.Game.Overlays.Settings.Sections private RealmContextFactory realmFactory { get; set; } private IDisposable realmSubscription; - private IQueryable realmSkins; + + private IQueryable realmSkins => + realmFactory.Context.All() + .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,13 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSkins = realmFactory.Context.All() - .Where(s => !s.DeletePending) - .OrderByDescending(s => s.Protected) // protected skins should be at the top. - .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); - - realmSubscription = realmSkins + realmSubscription = realmFactory.Register(realm => realmSkins .QueryAsyncWithNotifications((sender, changes, error) => { - if (changes == null) - return; - - // Eventually this should be handling the individual changes rather than refreshing the whole dropdown. + // 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(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f8cee2704b..6f3f467170 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -190,13 +190,13 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = getBeatmapSets(realmFactory.Context).QueryAsyncWithNotifications(beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.Context.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); + subscriptionSets = realmFactory.Register(realm => getBeatmapSets(realm).QueryAsyncWithNotifications(beatmapSetsChanged)); + subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.Context.All().Where(s => s.DeletePending && !s.Protected).QueryAsyncWithNotifications(deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realmFactory.Context.All().Where(b => b.Hidden).QueryAsyncWithNotifications(beatmapsChanged); + subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected).QueryAsyncWithNotifications(deletedBeatmapSetsChanged)); + subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -552,10 +552,11 @@ namespace osu.Game.Screens.Select private void signalBeatmapsLoaded() { - Debug.Assert(BeatmapSetsLoaded == false); - - BeatmapSetsChanged?.Invoke(); - BeatmapSetsLoaded = true; + if (!BeatmapSetsLoaded) + { + BeatmapSetsChanged?.Invoke(); + BeatmapSetsLoaded = true; + } itemsCache.Invalidate(); } diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 7ac99f4935..ee3930364b 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -48,18 +48,19 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realmFactory.Context.All() - .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) - .OrderByDescending(s => s.TotalScore) - .QueryAsyncWithNotifications((items, changes, ___) => - { - Rank = items.FirstOrDefault()?.Rank; - // Required since presence is changed via IsPresent override - Invalidate(Invalidation.Presence); - }); + scoreSubscription = realmFactory.Register(realm => + realm.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) + .OrderByDescending(s => s.TotalScore) + .QueryAsyncWithNotifications((items, changes, ___) => + { + Rank = items.FirstOrDefault()?.Rank; + // Required since presence is changed via IsPresent override + Invalidate(Invalidation.Presence); + })); }, true); } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index da52b43ab6..954c2a6413 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -44,9 +44,13 @@ namespace osu.Game.Screens.Select.Leaderboards beatmapInfo = value; Scores = null; - UpdateScores(); - if (IsLoaded) - refreshRealmSubscription(); + if (IsOnlineScope) + UpdateScores(); + else + { + if (IsLoaded) + refreshRealmSubscription(); + } } } @@ -109,15 +113,14 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realmFactory.Context.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) - .QueryAsyncWithNotifications((_, changes, ___) => - { - if (changes == null) - return; - - RefreshScores(); - }); + scoreSubscription = realmFactory.Register(realm => + realm.All() + .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) + .QueryAsyncWithNotifications((_, changes, ___) => + { + if (!IsOnlineScope) + RefreshScores(); + })); } protected override void Reset() diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index dd586bdd37..9fa5bb8562 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -79,17 +79,17 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.Context - .All() - .Where(s => !s.DeletePending) - .QueryAsyncWithNotifications((items, changes, ___) => - { - if (changes?.InsertedIndices == null) - return; + realmSubscription = realmContextFactory.Register(realm => + realm.All() + .Where(s => !s.DeletePending) + .QueryAsyncWithNotifications((items, changes, ___) => + { + if (changes?.InsertedIndices == null) + return; - foreach (int c in changes.InsertedIndices) - beatmapUpdated(items[c]); - }); + foreach (int c in changes.InsertedIndices) + beatmapUpdated(items[c]); + })); foreach ((int id, var _) in userMap) spectatorClient.WatchUser(id); From 63226f7def9f10d10460e43be72a139852f83cc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 19:56:32 +0900 Subject: [PATCH 08/85] Remove pointless initial `MusicController` beatmap set population Looks to pass tests and all usages look safe enough. --- osu.Game/Overlays/MusicController.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 01a2c9d354..c19a069343 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -87,12 +87,6 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); - - // 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.Detach()); - beatmapSubscription = realmFactory.Register(realm => availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged)); } From a86c0014fe3093069a23f7014c537d31f6fdf8bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 20:07:43 +0900 Subject: [PATCH 09/85] Remove unnecessary exception/check --- osu.Game/Overlays/MusicController.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c19a069343..0671e6d2c7 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -30,16 +30,7 @@ namespace osu.Game.Overlays [Resolved] private BeatmapManager beatmaps { get; set; } - public IBindableList 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 BeatmapSets => beatmapSets; /// /// Point in time after which the current track will be restarted on triggering a "previous track" action. From 3b11235d3c65c7416e8e092d0a8d4aeb45c0c85c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 20:45:10 +0900 Subject: [PATCH 10/85] Fix potentially cyclic subscription registration if a subscription's delegate accesses `Context` --- osu.Game/Database/RealmContextFactory.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c11ff61039..d9c66ccc69 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -88,6 +88,8 @@ namespace osu.Game.Database registerSubscription(action); } + Debug.Assert(context != null); + // creating a context will ensure our schema is up-to-date and migrated. return context; } @@ -261,10 +263,13 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); + // Get context outside of flag update to ensure beyond doubt this can't be cyclic. + var realm = Context; + lock (contextLock) { current_thread_subscriptions_allowed.Value = true; - subscriptionActions[action] = action(Context); + subscriptionActions[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } From 9b63f15e68577a2d2b88d2613a6f61aff554097a Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 21 Jan 2022 13:58:30 +0100 Subject: [PATCH 11/85] Add failing test --- .../Visual/Menus/TestSceneLoginPanel.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs index 4754a73f83..642cc68de5 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs @@ -8,6 +8,8 @@ using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays.Login; +using osu.Game.Users.Drawables; +using osuTK.Input; namespace osu.Game.Tests.Visual.Menus { @@ -15,6 +17,7 @@ namespace osu.Game.Tests.Visual.Menus public class TestSceneLoginPanel : OsuManualInputManagerTestScene { private LoginPanel loginPanel; + private int hideCount; [SetUpSteps] public void SetUpSteps() @@ -26,6 +29,7 @@ namespace osu.Game.Tests.Visual.Menus Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 0.5f, + RequestHide = () => hideCount++, }); }); } @@ -51,5 +55,22 @@ namespace osu.Game.Tests.Visual.Menus AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password"); AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); } + + [Test] + public void TestClickingOnFlagClosesPanel() + { + AddStep("reset hide count", () => hideCount = 0); + + AddStep("logout", () => API.Logout()); + AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password"); + AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); + + AddStep("click on flag", () => + { + InputManager.MoveMouseTo(loginPanel.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("hide requested", () => hideCount == 1); + } } } From 529610ee2e9c0d73c3a9c72c0531d4b716264644 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 21 Jan 2022 14:01:49 +0100 Subject: [PATCH 12/85] Call the UserPanel `Action` when clicking on the flag --- osu.Game/Users/Drawables/UpdateableFlag.cs | 8 ++++++++ osu.Game/Users/ExtendedUserPanel.cs | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 7db834bf83..e5debc0683 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,6 +24,12 @@ namespace osu.Game.Users.Drawables /// public bool ShowPlaceholderOnNull = true; + /// + /// Perform an action in addition to showing the country ranking. + /// This should be used to perform auxiliary tasks and not as a primary action for clicking a flag (to maintain a consistent UX). + /// + public Action Action; + public UpdateableFlag(Country country = null) { Country = country; @@ -52,6 +59,7 @@ namespace osu.Game.Users.Drawables protected override bool OnClick(ClickEvent e) { + Action?.Invoke(); rankingsOverlay?.ShowCountry(Country); return true; } diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index fc5e1eca5f..d0f693c37c 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -53,7 +53,8 @@ namespace osu.Game.Users protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.Country) { - Size = new Vector2(39, 26) + Size = new Vector2(39, 26), + Action = Action, }; protected SpriteIcon CreateStatusIcon() => statusIcon = new SpriteIcon From ad3a01dc06215e17458357189cac15b9c603d093 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 22:40:18 +0900 Subject: [PATCH 13/85] Use a more reliable method of reviving the update thread realm after blocking --- osu.Game/Database/RealmContextFactory.cs | 44 +++++++++++-------- .../Sections/DebugSettings/MemorySettings.cs | 4 -- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index d9c66ccc69..6fb9a2996b 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -69,30 +69,29 @@ namespace osu.Game.Database private Realm? context; - public Realm Context + public Realm Context => ensureUpdateContext(); + + private Realm ensureUpdateContext() { - get + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); + + lock (contextLock) { - if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); - - lock (contextLock) + if (context == null) { - if (context == null) - { - context = createContext(); - Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); + context = createContext(); + Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); - // Resubscribe any subscriptions - foreach (var action in subscriptionActions.Keys) - registerSubscription(action); - } - - Debug.Assert(context != null); - - // creating a context will ensure our schema is up-to-date and migrated. - return context; + // Resubscribe any subscriptions + foreach (var action in subscriptionActions.Keys) + registerSubscription(action); } + + Debug.Assert(context != null); + + // creating a context will ensure our schema is up-to-date and migrated. + return context; } } @@ -506,6 +505,8 @@ namespace osu.Game.Database if (isDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); + SynchronizationContext syncContext; + try { contextCreationLock.Wait(); @@ -515,6 +516,8 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread && context != null) throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + syncContext = SynchronizationContext.Current; + Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); context?.Dispose(); @@ -553,6 +556,9 @@ namespace osu.Game.Database { factory.contextCreationLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + + // Post back to the update thread to revive any subscriptions. + syncContext?.Post(_ => ensureUpdateContext(), null); }); } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 84a54b208c..8d4fc5fc9f 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -33,10 +33,6 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings using (realmFactory.BlockAllOperations()) { } - - // retrieve context to revive realm subscriptions. - // TODO: should we do this from OsuGame or RealmContextFactory or something? Answer: yes. - var _ = realmFactory.Context; } }, }; From 9946003069f7666f74bc383ac1371043cf6d0b35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 05:09:40 +0900 Subject: [PATCH 14/85] Update osu.Game/Screens/Menu/IntroScreen.cs Co-authored-by: Salman Ahmed --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index db9d9b83c4..10be90a5f0 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Menu int setCount = sets.Count; - if (sets.Any()) + if (setCount > 0) { var found = sets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); From 955bab926fac76513a660737a42e48e01cfd9bc0 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sat, 22 Jan 2022 19:38:56 +0100 Subject: [PATCH 15/85] Separate the settings for each modes radiuses --- .../Mods/CatchModFlashlight.cs | 19 ++++++++-- .../Mods/ManiaModFlashlight.cs | 19 ++++++++-- .../Mods/OsuModFlashlight.cs | 36 ++++++++++++------- .../Mods/TaikoModFlashlight.cs | 19 ++++++++-- osu.Game/Rulesets/Mods/ModFlashlight.cs | 20 ++--------- 5 files changed, 75 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 9d9fa5aed4..f75772b04e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -3,6 +3,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Configuration; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; @@ -15,8 +16,22 @@ namespace osu.Game.Rulesets.Catch.Mods { public override double ScoreMultiplier => 1.12; - public override bool DefaultComboDependency => true; - public override float DefaultRadius => 350; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = true, + Value = true + }; + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 150f, + MaxValue = 600f, + Default = 350f, + Value = 350f, + Precision = 5f + }; public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 4fff736c57..a6a3c3be73 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; +using osu.Game.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osuTK; @@ -16,8 +17,22 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; - public override bool DefaultComboDependency => false; - public override float DefaultRadius => 180; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = false, + Value = false + }; + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 0f, + MaxValue = 230f, + Default = 50f, + Value = 50f, + Precision = 5f + }; public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index f381d14ffe..e2a6d0f0dc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -20,14 +20,32 @@ namespace osu.Game.Rulesets.Osu.Mods { public override double ScoreMultiplier => 1.12; - public override bool DefaultComboDependency => true; - - //private const float default_flashlight_size = 180; - public override float DefaultRadius => 180; - private const double default_follow_delay = 120; + [SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")] + public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay) + { + MinValue = default_follow_delay, + MaxValue = default_follow_delay * 10, + Precision = default_follow_delay, + }; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = true, + Value = true + }; + + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 90f, + MaxValue = 360f, + Default = 180f, + Value = 180f, + Precision = 5f + }; private OsuFlashlight flashlight; @@ -39,14 +57,6 @@ namespace osu.Game.Rulesets.Osu.Mods s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; } - [SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")] - public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay) - { - MinValue = default_follow_delay, - MaxValue = default_follow_delay * 10, - Precision = default_follow_delay, - }; - private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { public double FollowDelay { private get; set; } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 76f7c45b75..71c9d777ec 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -4,6 +4,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -16,10 +17,22 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => 1.12; - public override bool DefaultComboDependency => true; + [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] + public override BindableBool ChangeRadius { get; } = new BindableBool + { + Default = true, + Value = true + }; - //private const float default_flashlight_size = 250; - public override float DefaultRadius => 250; + [SettingSource("Initial radius", "Initial radius of the flashlight area.")] + public override BindableNumber InitialRadius { get; } = new BindableNumber + { + MinValue = 0f, + MaxValue = 400f, + Default = 250f, + Value = 250f, + Precision = 5f + }; public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index c218ab45fe..51006d96e8 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -34,26 +34,10 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Restricted view area."; [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public Bindable ChangeRadius { get; private set; } + public abstract BindableBool ChangeRadius { get; } [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public BindableNumber InitialRadius { get; private set; } - - public abstract float DefaultRadius { get; } - - public abstract bool DefaultComboDependency { get; } - - internal ModFlashlight() - { - InitialRadius = new BindableFloat(DefaultRadius) - { - MinValue = DefaultRadius * .5f, - MaxValue = DefaultRadius * 1.5f, - Precision = 5f, - }; - - ChangeRadius = new BindableBool(DefaultComboDependency); - } + public abstract BindableNumber InitialRadius { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From 735414bc49381df02d6fb243c25054ba8f35e204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 17:27:27 +0100 Subject: [PATCH 16/85] Replace `TimeSignatures` enum with struct for storage of arbitrary meter --- .../Skinning/Default/CirclePiece.cs | 2 +- .../Formats/LegacyBeatmapDecoderTest.cs | 6 +-- .../NonVisual/BarLineGeneratorTest.cs | 6 +-- .../TestSceneNightcoreBeatContainer.cs | 4 +- .../ControlPoints/TimingControlPoint.cs | 4 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/Timing/TimeSignature.cs | 45 +++++++++++++++++++ osu.Game/Beatmaps/Timing/TimeSignatures.cs | 4 +- osu.Game/Rulesets/Mods/Metronome.cs | 2 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 10 ++--- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 6 +-- .../Timeline/TimelineTickDisplay.cs | 2 +- .../RowAttributes/TimingRowAttribute.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingSection.cs | 11 +++-- osu.Game/Screens/Menu/MenuSideFlashes.cs | 4 +- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 17 files changed, 84 insertions(+), 32 deletions(-) create mode 100644 osu.Game/Beatmaps/Timing/TimeSignature.cs diff --git a/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs index 8ca996159b..a106c4f629 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Default/CirclePiece.cs @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default if (!effectPoint.KiaiMode) return; - if (beatIndex % (int)timingPoint.TimeSignature != 0) + if (beatIndex % timingPoint.TimeSignature.Numerator != 0) return; double duration = timingPoint.BeatLength * 2; diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 6ec14e6351..0459753b28 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -178,17 +178,17 @@ namespace osu.Game.Tests.Beatmaps.Formats var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); Assert.AreEqual(329.67032967033, timingPoint.BeatLength); - Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature); timingPoint = controlPoints.TimingPointAt(48428); Assert.AreEqual(956, timingPoint.Time); Assert.AreEqual(329.67032967033d, timingPoint.BeatLength); - Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature); timingPoint = controlPoints.TimingPointAt(119637); Assert.AreEqual(119637, timingPoint.Time); Assert.AreEqual(659.340659340659, timingPoint.BeatLength); - Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature); var difficultyPoint = controlPoints.DifficultyPointAt(0); Assert.AreEqual(0, difficultyPoint.Time); diff --git a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs index 834c05fd08..6ae8231deb 100644 --- a/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs +++ b/osu.Game.Tests/NonVisual/BarLineGeneratorTest.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.NonVisual const int beat_length_numerator = 2000; const int beat_length_denominator = 7; - const TimeSignatures signature = TimeSignatures.SimpleQuadruple; + TimeSignature signature = TimeSignature.SimpleQuadruple; var beatmap = new Beatmap { @@ -49,7 +49,7 @@ namespace osu.Game.Tests.NonVisual for (int i = 0; i * beat_length_denominator < barLines.Count; i++) { var barLine = barLines[i * beat_length_denominator]; - int expectedTime = beat_length_numerator * (int)signature * i; + int expectedTime = beat_length_numerator * signature.Numerator * i; // every seventh bar's start time should be at least greater than the whole number we expect. // It cannot be less, as that can affect overlapping scroll algorithms @@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime)); // check major/minor lines for good measure too - Assert.AreEqual(i % (int)signature == 0, barLine.Major); + Assert.AreEqual(i % signature.Numerator == 0, barLine.Major); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs index 951ee1489d..759e4fa4ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs @@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual.Gameplay Add(new ModNightcore.NightcoreBeatContainer()); - AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleQuadruple)); - AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleTriple)); + AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleQuadruple)); + AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignature.SimpleTriple)); } } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index ec20328fab..922439fcb8 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time signature at this control point. /// - public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple }; + public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignature.SimpleQuadruple); /// /// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing. @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time signature at this control point. /// - public TimeSignatures TimeSignature + public TimeSignature TimeSignature { get => TimeSignatureBindable.Value; set => TimeSignatureBindable.Value = value; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 893eb8ab78..8f3f05aa9f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -340,9 +340,9 @@ namespace osu.Game.Beatmaps.Formats double beatLength = Parsing.ParseDouble(split[1].Trim()); double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; - TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; + TimeSignature timeSignature = TimeSignature.SimpleQuadruple; if (split.Length >= 3) - timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]); + timeSignature = split[2][0] == '0' ? TimeSignature.SimpleQuadruple : new TimeSignature(Parsing.ParseInt(split[2])); LegacySampleBank sampleSet = defaultSampleBank; if (split.Length >= 4) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index ebdc882d2f..4cf6d3335f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -227,7 +227,7 @@ namespace osu.Game.Beatmaps.Formats if (effectPoint.OmitFirstBarLine) effectFlags |= LegacyEffectFlags.OmitFirstBarLine; - writer.Write(FormattableString.Invariant($"{(int)legacyControlPoints.TimingPointAt(time).TimeSignature},")); + writer.Write(FormattableString.Invariant($"{legacyControlPoints.TimingPointAt(time).TimeSignature.Numerator},")); writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},")); writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); diff --git a/osu.Game/Beatmaps/Timing/TimeSignature.cs b/osu.Game/Beatmaps/Timing/TimeSignature.cs new file mode 100644 index 0000000000..5bfeea5e9b --- /dev/null +++ b/osu.Game/Beatmaps/Timing/TimeSignature.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Beatmaps.Timing +{ + /// + /// Stores the time signature of a track. + /// For now, the lower numeral can only be 4; support for other denominators can be considered at a later date. + /// + public class TimeSignature : IEquatable + { + /// + /// The numerator of a signature. + /// + public int Numerator { get; } + + // TODO: support time signatures with a denominator other than 4 + // this in particular requires a new beatmap format. + + public TimeSignature(int numerator) + { + if (numerator < 1) + throw new ArgumentOutOfRangeException(nameof(numerator), numerator, "The numerator of a time signature must be positive."); + + Numerator = numerator; + } + + public static TimeSignature SimpleTriple => new TimeSignature(3); + public static TimeSignature SimpleQuadruple => new TimeSignature(4); + + public override string ToString() => $"{Numerator}/4"; + + public bool Equals(TimeSignature other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Numerator == other.Numerator; + } + + public override int GetHashCode() => Numerator; + } +} diff --git a/osu.Game/Beatmaps/Timing/TimeSignatures.cs b/osu.Game/Beatmaps/Timing/TimeSignatures.cs index 33e6342ae6..d783d3f9ec 100644 --- a/osu.Game/Beatmaps/Timing/TimeSignatures.cs +++ b/osu.Game/Beatmaps/Timing/TimeSignatures.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.ComponentModel; namespace osu.Game.Beatmaps.Timing { - public enum TimeSignatures + [Obsolete("Use osu.Game.Beatmaps.Timing.TimeSignature instead.")] + public enum TimeSignatures // can be removed 20220722 { [Description("4/4")] SimpleQuadruple = 4, diff --git a/osu.Game/Rulesets/Mods/Metronome.cs b/osu.Game/Rulesets/Mods/Metronome.cs index 8b6d86c45f..b85a341577 100644 --- a/osu.Game/Rulesets/Mods/Metronome.cs +++ b/osu.Game/Rulesets/Mods/Metronome.cs @@ -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) diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index a44967c21c..993efead33 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mods { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - int beatsPerBar = (int)timingPoint.TimeSignature; + int beatsPerBar = timingPoint.TimeSignature.Numerator; int segmentLength = beatsPerBar * Divisor * bars_per_segment; if (!IsBeatSyncedWithTrack) @@ -102,14 +102,14 @@ namespace osu.Game.Rulesets.Mods playBeatFor(beatIndex % segmentLength, timingPoint.TimeSignature); } - private void playBeatFor(int beatIndex, TimeSignatures signature) + private void playBeatFor(int beatIndex, TimeSignature signature) { if (beatIndex == 0) finishSample?.Play(); - switch (signature) + switch (signature.Numerator) { - case TimeSignatures.SimpleTriple: + case 3: switch (beatIndex % 6) { case 0: @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Mods break; - case TimeSignatures.SimpleQuadruple: + case 4: switch (beatIndex % 4) { case 0: diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index e78aa5a5a0..d71a499119 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -41,9 +41,9 @@ namespace osu.Game.Rulesets.Objects int currentBeat = 0; // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object - double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - currentTimingPoint.BeatLength : lastHitTime + currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature; + double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - currentTimingPoint.BeatLength : lastHitTime + currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; - double barLength = currentTimingPoint.BeatLength * (int)currentTimingPoint.TimeSignature; + double barLength = currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator; for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++) { @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Objects BarLines.Add(new TBarLine { StartTime = t, - Major = currentBeat % (int)currentTimingPoint.TimeSignature == 0 + Major = currentBeat % currentTimingPoint.TimeSignature.Numerator == 0 }); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 1415014e59..cc4041394d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -125,7 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (beat == 0 && i == 0) nextMinTick = float.MinValue; - int indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value); + int indexInBar = beat % (point.TimeSignature.Numerator * beatDivisor.Value); int divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); var colour = BindableBeatDivisor.GetColourFor(divisor, colours); diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs index ab840e56a7..f8ec4aef25 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes public class TimingRowAttribute : RowAttribute { private readonly BindableNumber beatLength; - private readonly Bindable timeSignature; + private readonly Bindable timeSignature; private OsuSpriteText text; public TimingRowAttribute(TimingControlPoint timing) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index a0bb9ac506..e0b09ce980 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Edit.Timing internal class TimingSection : Section { private SettingsSlider bpmSlider; - private SettingsEnumDropdown timeSignature; + private SettingsDropdown timeSignature; private BPMTextBox bpmTextEntry; [BackgroundDependencyLoader] @@ -25,9 +25,14 @@ namespace osu.Game.Screens.Edit.Timing { bpmTextEntry = new BPMTextBox(), bpmSlider = new BPMSlider(), - timeSignature = new SettingsEnumDropdown + timeSignature = new SettingsDropdown { - LabelText = "Time Signature" + LabelText = "Time Signature", + Items = new[] + { + TimeSignature.SimpleTriple, + TimeSignature.SimpleQuadruple + } }, }); } diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index bdcd3020f8..cd0c75c1a1 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -94,9 +94,9 @@ namespace osu.Game.Screens.Menu if (beatIndex < 0) return; - if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % (int)timingPoint.TimeSignature == 0) + if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % timingPoint.TimeSignature.Numerator == 0) flash(leftBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes); - if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % (int)timingPoint.TimeSignature == 0) + if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % timingPoint.TimeSignature.Numerator == 0) flash(rightBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes); } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index f9388097ac..c82efe2d32 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -282,7 +282,7 @@ namespace osu.Game.Screens.Menu { this.Delay(early_activation).Schedule(() => { - if (beatIndex % (int)timingPoint.TimeSignature == 0) + if (beatIndex % timingPoint.TimeSignature.Numerator == 0) sampleDownbeat.Play(); else sampleBeat.Play(); From f39f2c93b52526a3e5d5aa06e18d0f0ac85020c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 18:54:14 +0100 Subject: [PATCH 17/85] Add control for arbitrary-numerator time signatures --- .../Editing/TestSceneLabelledTimeSignature.cs | 88 ++++++++++++++++++ .../Edit/Timing/LabelledTimeSignature.cs | 93 +++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs create mode 100644 osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs new file mode 100644 index 0000000000..cedbb5025f --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs @@ -0,0 +1,88 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Timing; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneLabelledTimeSignature : OsuManualInputManagerTestScene + { + private LabelledTimeSignature timeSignature; + + private void createLabelledTimeSignature(TimeSignature initial) => AddStep("create labelled time signature", () => + { + Child = timeSignature = new LabelledTimeSignature + { + Label = "Time Signature", + RelativeSizeAxes = Axes.None, + Width = 400, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { Value = initial } + }; + }); + + private OsuTextBox numeratorTextBox => timeSignature.ChildrenOfType().Single(); + + [Test] + public void TestInitialValue() + { + createLabelledTimeSignature(TimeSignature.SimpleTriple); + AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple)); + } + + [Test] + public void TestChangeViaCurrent() + { + createLabelledTimeSignature(TimeSignature.SimpleQuadruple); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("set current to 5/4", () => timeSignature.Current.Value = new TimeSignature(5)); + + AddAssert("current is 5/4", () => timeSignature.Current.Value.Equals(new TimeSignature(5))); + AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "5"); + + AddStep("set current to 3/4", () => timeSignature.Current.Value = TimeSignature.SimpleTriple); + + AddAssert("current is 3/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleTriple)); + AddAssert("numerator is 5", () => numeratorTextBox.Current.Value == "3"); + } + + [Test] + public void TestChangeNumerator() + { + createLabelledTimeSignature(TimeSignature.SimpleQuadruple); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox)); + + AddStep("set numerator to 7", () => numeratorTextBox.Current.Value = "7"); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("drop focus", () => InputManager.ChangeFocus(null)); + AddAssert("current is 7/4", () => timeSignature.Current.Value.Equals(new TimeSignature(7))); + } + + [Test] + public void TestInvalidChangeRollbackOnCommit() + { + createLabelledTimeSignature(TimeSignature.SimpleQuadruple); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("focus text box", () => InputManager.ChangeFocus(numeratorTextBox)); + + AddStep("set numerator to 0", () => numeratorTextBox.Current.Value = "0"); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + + AddStep("drop focus", () => InputManager.ChangeFocus(null)); + AddAssert("current is 4/4", () => timeSignature.Current.Value.Equals(TimeSignature.SimpleQuadruple)); + AddAssert("numerator is 4", () => numeratorTextBox.Current.Value == "4"); + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs new file mode 100644 index 0000000000..52aece75ad --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class LabelledTimeSignature : LabelledComponent + { + public LabelledTimeSignature() + : base(false) + { + } + + protected override TimeSignatureBox CreateComponent() => new TimeSignatureBox(); + + internal class TimeSignatureBox : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(TimeSignature.SimpleQuadruple); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private OsuNumberBox numeratorBox; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + numeratorBox = new OsuNumberBox + { + Width = 40, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + CornerRadius = CORNER_RADIUS, + CommitOnFocusLost = true + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding(10), + Text = "/4", + Font = OsuFont.Default.With(size: 20) + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => updateFromCurrent(), true); + numeratorBox.OnCommit += (_, __) => updateFromNumeratorBox(); + } + + private void updateFromCurrent() + { + numeratorBox.Current.Value = Current.Value.Numerator.ToString(); + } + + private void updateFromNumeratorBox() + { + if (int.TryParse(numeratorBox.Current.Value, out int numerator) && numerator > 0) + Current.Value = new TimeSignature(numerator); + else + { + // trigger `Current` change to restore the numerator box's text to a valid value. + Current.TriggerChange(); + } + } + } + } +} From 54f7b1b8d0cec1638c67492fadf7c7fca8577f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 22 Jan 2022 18:54:28 +0100 Subject: [PATCH 18/85] Use new time signature control on timing screen --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index e0b09ce980..cd0b56d338 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Timing; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; @@ -15,7 +14,7 @@ namespace osu.Game.Screens.Edit.Timing internal class TimingSection : Section { private SettingsSlider bpmSlider; - private SettingsDropdown timeSignature; + private LabelledTimeSignature timeSignature; private BPMTextBox bpmTextEntry; [BackgroundDependencyLoader] @@ -25,15 +24,10 @@ namespace osu.Game.Screens.Edit.Timing { bpmTextEntry = new BPMTextBox(), bpmSlider = new BPMSlider(), - timeSignature = new SettingsDropdown + timeSignature = new LabelledTimeSignature { - LabelText = "Time Signature", - Items = new[] - { - TimeSignature.SimpleTriple, - TimeSignature.SimpleQuadruple - } - }, + Label = "Time Signature" + } }); } From 1ea5a2e6a75301341355f7a934b6b79a5f282d7c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jan 2022 10:11:07 +0300 Subject: [PATCH 19/85] Fix incorrect assert step name --- osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs index cedbb5025f..b34974dfc7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneLabelledTimeSignature.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Editing 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 5", () => numeratorTextBox.Current.Value == "3"); + AddAssert("numerator is 3", () => numeratorTextBox.Current.Value == "3"); } [Test] From e4758c9dbbee726593078de2c111fd243b4db582 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jan 2022 10:13:32 +0300 Subject: [PATCH 20/85] Mark `LabelledTimeSignature` as public --- osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs index 52aece75ad..66bd341393 100644 --- a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs +++ b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Screens.Edit.Timing { - internal class LabelledTimeSignature : LabelledComponent + public class LabelledTimeSignature : LabelledComponent { public LabelledTimeSignature() : base(false) @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Edit.Timing protected override TimeSignatureBox CreateComponent() => new TimeSignatureBox(); - internal class TimeSignatureBox : CompositeDrawable, IHasCurrentValue + public class TimeSignatureBox : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(TimeSignature.SimpleQuadruple); From a5493ce0d1f70b6db0d9ce846ed14c42f360946e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 17:51:32 +0900 Subject: [PATCH 21/85] Fix incorrect nesting of statements causing completely broken logic --- osu.Game/Screens/Menu/IntroScreen.cs | 54 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 10be90a5f0..e66ecc74e1 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -99,51 +99,51 @@ namespace osu.Game.Screens.Menu { realmContextFactory.Run(realm => { - var sets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + var usableBeatmapSets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); - int setCount = sets.Count; + int setCount = usableBeatmapSets.Count; if (setCount > 0) { - var found = sets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); + var found = usableBeatmapSets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault(); if (found != null) initialBeatmap = beatmaps.GetWorkingBeatmap(found); } }); + } - // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. - if (initialBeatmap == null) + // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. + if (initialBeatmap == null) + { + if (!loadThemedIntro()) { - if (!loadThemedIntro()) - { - // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. - // this could happen if a user has nuked their files store. for now, reimport to repair this. - var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); + // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. + // this could happen if a user has nuked their files store. for now, reimport to repair this. + var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); - import?.PerformWrite(b => b.Protected = true); + import?.PerformWrite(b => b.Protected = true); - loadThemedIntro(); - } + loadThemedIntro(); } + } - bool loadThemedIntro() + bool loadThemedIntro() + { + var setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + + if (setInfo == null) + return false; + + setInfo.PerformRead(s => { - var setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + if (s.Beatmaps.Count == 0) + return; - if (setInfo == null) - return false; + initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); + }); - setInfo.PerformRead(s => - { - if (s.Beatmaps.Count == 0) - return; - - initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First()); - }); - - return UsingThemedIntro = initialBeatmap != null; - } + return UsingThemedIntro = initialBeatmap != null; } } From 70a120ea8a5e7694c068624b6495fa857f95a4e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 18:00:24 +0900 Subject: [PATCH 22/85] Add missing lock coverage when using `subscriptionActions` dictionary --- osu.Game/Database/RealmContextFactory.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 6fb9a2996b..606871337c 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -249,11 +249,13 @@ namespace osu.Game.Database return new InvokeOnDisposal(() => { - // TODO: this likely needs to be run on the update thread. - if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + lock (contextLock) { - unsubscriptionAction?.Dispose(); - subscriptionActions.Remove(action); + if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + { + unsubscriptionAction?.Dispose(); + subscriptionActions.Remove(action); + } } }); } From bd0eda7e908e7366198e0007d6db9434faded441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 18:01:39 +0900 Subject: [PATCH 23/85] Use method instead of property for realm query retrieval --- .../Bindings/DatabasedKeyBindingContainer.cs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 5a9797ffce..ac33c64391 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -46,19 +46,16 @@ namespace osu.Game.Input.Bindings throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); } - private IQueryable realmKeyBindings + private IQueryable queryRealmKeyBindings() { - get - { - string rulesetName = ruleset?.ShortName; - return realmFactory.Context.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); - } + string rulesetName = ruleset?.ShortName; + return realmFactory.Context.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); } protected override void LoadComplete() { - realmSubscription = realmFactory.Register(realm => realmKeyBindings + realmSubscription = realmFactory.Register(realm => queryRealmKeyBindings() .QueryAsyncWithNotifications((sender, changes, error) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, @@ -80,11 +77,11 @@ namespace osu.Game.Input.Bindings { var defaults = DefaultKeyBindings.ToList(); - List newBindings = realmKeyBindings.Detach() - // this ordering is important to ensure that we read entries from the database in the order - // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise - // have been eaten by the music controller due to query order. - .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); + List newBindings = queryRealmKeyBindings().Detach() + // this ordering is important to ensure that we read entries from the database in the order + // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise + // have been eaten by the music controller due to query order. + .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); // In the case no bindings were found in the database, presume this usage is for a non-databased ruleset. // This actually should never be required and can be removed if it is ever deemed to cause a problem. From f39ff1eacb3b5179f70aa47f0a892d23e2651e7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 18:13:28 +0900 Subject: [PATCH 24/85] Add unregistration on blocking This is the first part of the requirement of sending a `ChangeSet` event to ensure correct state during blocking time --- osu.Game/Database/RealmContextFactory.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 606871337c..3e0f278e30 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -247,6 +247,8 @@ namespace osu.Game.Database registerSubscription(action); + // This token is returned to the consumer only. + // It will cause the registration to be permanently removed. return new InvokeOnDisposal(() => { lock (contextLock) @@ -269,12 +271,27 @@ namespace osu.Game.Database lock (contextLock) { + Debug.Assert(!customSubscriptionActions.TryGetValue(action, out var found) || found == null); + current_thread_subscriptions_allowed.Value = true; subscriptionActions[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } + /// + /// Unregister all subscriptions when the realm context is to be recycled. + /// Subscriptions will still remain and will be re-subscribed when the realm context returns. + /// + private void unregisterAllSubscriptions() + { + foreach (var action in subscriptionActions) + { + action.Value?.Dispose(); + subscriptionActions[action.Key] = null; + } + } + private Realm createContext() { if (isDisposed) @@ -519,6 +536,7 @@ namespace osu.Game.Database throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); syncContext = SynchronizationContext.Current; + unregisterAllSubscriptions(); Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); From 61cef42be977be4fe3cd612122de1f52556e03d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 19:42:26 +0900 Subject: [PATCH 25/85] Proof of concept realm subscriptions via `Register` --- osu.Game/Database/EmptyRealmSet.cs | 74 ++++++++++++++++++++ osu.Game/Database/RealmContextFactory.cs | 33 +++++++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 21 +++--- 4 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Database/EmptyRealmSet.cs diff --git a/osu.Game/Database/EmptyRealmSet.cs b/osu.Game/Database/EmptyRealmSet.cs new file mode 100644 index 0000000000..2fecfcbe07 --- /dev/null +++ b/osu.Game/Database/EmptyRealmSet.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using Realms; +using Realms.Schema; + +#nullable enable + +namespace osu.Game.Database +{ + public class EmptyRealmSet : IRealmCollection + { + private static List emptySet => new List(); + + public IEnumerator GetEnumerator() + { + return emptySet.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)emptySet).GetEnumerator(); + } + + public int Count => emptySet.Count; + + public T this[int index] => emptySet[index]; + + 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 int IndexOf(object item) + { + return emptySet.IndexOf((T)item); + } + + public bool Contains(object item) + { + return emptySet.Contains((T)item); + } + + public IRealmCollection Freeze() + { + throw new NotImplementedException(); + } + + public IDisposable SubscribeForNotifications(NotificationCallbackDelegate callback) + { + throw new NotImplementedException(); + } + + public bool IsValid => throw new NotImplementedException(); + + public Realm Realm => throw new NotImplementedException(); + + public ObjectSchema ObjectSchema => throw new NotImplementedException(); + + public bool IsFrozen => throw new NotImplementedException(); + } +} diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 3e0f278e30..32f7ac99c1 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -84,7 +84,7 @@ namespace osu.Game.Database Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); // Resubscribe any subscriptions - foreach (var action in subscriptionActions.Keys) + foreach (var action in customSubscriptionActions.Keys) registerSubscription(action); } @@ -233,7 +233,22 @@ namespace osu.Game.Database } } - private readonly Dictionary, IDisposable?> subscriptionActions = new Dictionary, IDisposable?>(); + private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); + + private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); + + public IDisposable Register(Func> query, NotificationCallbackDelegate onChanged) + where T : RealmObjectBase + { + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + + realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); + + return Register(action); + + IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); + } /// /// Run work on realm that will be run every time the update thread realm context gets recycled. @@ -253,10 +268,11 @@ namespace osu.Game.Database { lock (contextLock) { - if (subscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) { unsubscriptionAction?.Dispose(); - subscriptionActions.Remove(action); + customSubscriptionActions.Remove(action); + realmSubscriptionsResetMap.Remove(action); } } }); @@ -274,7 +290,7 @@ namespace osu.Game.Database Debug.Assert(!customSubscriptionActions.TryGetValue(action, out var found) || found == null); current_thread_subscriptions_allowed.Value = true; - subscriptionActions[action] = action(realm); + customSubscriptionActions[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } @@ -285,10 +301,13 @@ namespace osu.Game.Database /// private void unregisterAllSubscriptions() { - foreach (var action in subscriptionActions) + foreach (var action in realmSubscriptionsResetMap.Values) + action(); + + foreach (var action in customSubscriptionActions) { action.Value?.Dispose(); - subscriptionActions[action.Key] = null; + customSubscriptionActions[action.Key] = null; } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6f3f467170..10ba23985e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -190,7 +190,7 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = realmFactory.Register(realm => getBeatmapSets(realm).QueryAsyncWithNotifications(beatmapSetsChanged)); + subscriptionSets = realmFactory.Register(getBeatmapSets, beatmapSetsChanged); subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); // Can't use main subscriptions because we can't lookup deleted indices. @@ -274,7 +274,7 @@ namespace osu.Game.Screens.Select } } - private IRealmCollection getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + private IQueryable getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected); public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => removeBeatmapSet(beatmapSet.ID); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 9fa5bb8562..904648f727 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -17,6 +17,7 @@ using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Scoring; +using Realms; namespace osu.Game.Screens.Spectate { @@ -79,23 +80,21 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.Register(realm => - realm.All() - .Where(s => !s.DeletePending) - .QueryAsyncWithNotifications((items, changes, ___) => - { - if (changes?.InsertedIndices == null) - return; - - foreach (int c in changes.InsertedIndices) - beatmapUpdated(items[c]); - })); + realmSubscription = realmContextFactory.Register( + realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); foreach ((int id, var _) in userMap) spectatorClient.WatchUser(id); })); } + private void beatmapsChanged(IRealmCollection items, ChangeSet changes, Exception ___) + { + if (changes?.InsertedIndices == null) return; + + foreach (int c in changes.InsertedIndices) beatmapUpdated(items[c]); + } + private void beatmapUpdated(BeatmapSetInfo beatmapSet) { foreach ((int userId, _) in userMap) From e9e3e024a19d4867673152efa06f89842937d032 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 19:50:29 +0900 Subject: [PATCH 26/85] Update all usages of `QueryAsyncWithNotifications` to use new `Register` pathway --- .../Bindings/DatabasedKeyBindingContainer.cs | 13 +++++----- osu.Game/Online/BeatmapDownloadTracker.cs | 4 ++-- .../OnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- osu.Game/Online/ScoreDownloadTracker.cs | 4 ++-- osu.Game/Overlays/MusicController.cs | 2 +- .../Overlays/Settings/Sections/SkinSection.cs | 19 +++++++-------- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++--- .../Screens/Select/Carousel/TopLocalRank.cs | 24 +++++++++---------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 14 +++++------ 9 files changed, 44 insertions(+), 46 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index ac33c64391..4ad5693867 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -55,13 +55,12 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realmFactory.Register(realm => queryRealmKeyBindings() - .QueryAsyncWithNotifications((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(); - })); + realmSubscription = realmFactory.Register(realm => 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(); + }); base.LoadComplete(); } diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index d50ab5a347..70599a167b 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -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.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -54,7 +54,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(beatmapSetInfo)); }); } - })); + }); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index fdcf2b39c6..8dd28f5417 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -78,13 +78,13 @@ 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 = realmContextFactory.Register(realm => filteredBeatmaps().QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; Scheduler.AddOnce(updateAvailability); - })); + }); }, true); } diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index de1d6fd94a..72e95bd6df 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending).QueryAsyncWithNotifications((items, changes, ___) => + realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); @@ -59,7 +59,7 @@ namespace osu.Game.Online attachDownload(Downloader.GetExistingDownload(scoreInfo)); }); } - })); + }); } private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() => diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 01a2c9d354..24d907285a 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -93,7 +93,7 @@ namespace osu.Game.Overlays foreach (var s in availableBeatmaps) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.Register(realm => availableBeatmaps.QueryAsyncWithNotifications(beatmapsChanged)); + beatmapSubscription = realmFactory.Register(realm => availableBeatmaps, beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 11a7275168..c767edec71 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections private IDisposable realmSubscription; - private IQueryable realmSkins => + private IQueryable queryRealmSkins() => realmFactory.Context.All() .Where(s => !s.DeletePending) .OrderByDescending(s => s.Protected) // protected skins should be at the top. @@ -83,13 +83,12 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realmFactory.Register(realm => realmSkins - .QueryAsyncWithNotifications((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(); - })); + realmSubscription = realmFactory.Register(realm => 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(); @@ -129,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(realmFactory); skinItems.Insert(protectedCount, random_skin_info); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 10ba23985e..889a6f5b79 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -191,12 +191,12 @@ namespace osu.Game.Screens.Select base.LoadComplete(); subscriptionSets = realmFactory.Register(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); + subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected).QueryAsyncWithNotifications(deletedBeatmapSetsChanged)); - subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden).QueryAsyncWithNotifications(beatmapsChanged)); + subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index ee3930364b..fc2188e597 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -49,18 +49,18 @@ namespace osu.Game.Screens.Select.Carousel { scoreSubscription?.Dispose(); scoreSubscription = realmFactory.Register(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) - .OrderByDescending(s => s.TotalScore) - .QueryAsyncWithNotifications((items, changes, ___) => - { - Rank = items.FirstOrDefault()?.Rank; - // Required since presence is changed via IsPresent override - Invalidate(Invalidation.Presence); - })); + realm.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) + .OrderByDescending(s => s.TotalScore), + (items, changes, ___) => + { + Rank = items.FirstOrDefault()?.Rank; + // Required since presence is changed via IsPresent override + Invalidate(Invalidation.Presence); + }); }, true); } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 954c2a6413..463f878e2a 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -114,13 +114,13 @@ namespace osu.Game.Screens.Select.Leaderboards return; scoreSubscription = realmFactory.Register(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID) - .QueryAsyncWithNotifications((_, changes, ___) => - { - if (!IsOnlineScope) - RefreshScores(); - })); + realm.All() + .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), + (_, changes, ___) => + { + if (!IsOnlineScope) + RefreshScores(); + }); } protected override void Reset() From db8639435538108982acffad2d9e8da710dd71e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 20:27:35 +0900 Subject: [PATCH 27/85] Fix `TestResources` returning a test `BeatmapSetInfo` that can't be laoded directly into realm --- osu.Game.Tests/Resources/TestResources.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index d2cab09ac9..81b624f908 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -80,7 +80,10 @@ namespace osu.Game.Tests.Resources public static BeatmapSetInfo CreateTestBeatmapSetInfo(int? difficultyCount = null, RulesetInfo[] rulesets = null) { int j = 0; - RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length] ?? new OsuRuleset().RulesetInfo; + + rulesets ??= new[] { new OsuRuleset().RulesetInfo }; + + RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length]; int setId = Interlocked.Increment(ref importId); From 0709a2ac9be3376193a4aeb266b497683b1bde7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 20:28:13 +0900 Subject: [PATCH 28/85] Add test coverage of realm subscription scenarios --- .../RealmSubscriptionRegistrationTests.cs | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs new file mode 100644 index 0000000000..16e9f31c7b --- /dev/null +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -0,0 +1,138 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Tests.Resources; +using Realms; + +#nullable enable + +namespace osu.Game.Tests.Database +{ + [TestFixture] + public class RealmSubscriptionRegistrationTests : RealmTest + { + [Test] + public void TestSubscriptionWithContextLoss() + { + IEnumerable? resolvedItems = null; + ChangeSet? lastChanges = null; + + RunTestWithRealm((realmFactory, _) => + { + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + var registration = realmFactory.Register(realm => realm.All(), onChanged); + + testEventsArriving(true); + + // All normal until here. + // Now let's yank the main realm context. + resolvedItems = null; + lastChanges = null; + + using (realmFactory.BlockAllOperations()) + Assert.That(resolvedItems, Is.Empty); + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + testEventsArriving(true); + + // Now let's try unsubscribing. + resolvedItems = null; + lastChanges = null; + + registration.Dispose(); + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + testEventsArriving(false); + + // And make sure even after another context loss we don't get firings. + using (realmFactory.BlockAllOperations()) + Assert.That(resolvedItems, Is.Null); + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + testEventsArriving(false); + + void testEventsArriving(bool shouldArrive) + { + realmFactory.Run(realm => realm.Refresh()); + + if (shouldArrive) + Assert.That(resolvedItems, Has.One.Items); + else + Assert.That(resolvedItems, Is.Null); + + realmFactory.Write(realm => + { + realm.RemoveAll(); + realm.RemoveAll(); + }); + + realmFactory.Run(realm => realm.Refresh()); + + if (shouldArrive) + Assert.That(lastChanges?.DeletedIndices, Has.One.Items); + else + Assert.That(lastChanges, Is.Null); + } + }); + + void onChanged(IRealmCollection sender, ChangeSet? changes, Exception error) + { + if (changes == null) + resolvedItems = sender; + + lastChanges = changes; + } + } + + [Test] + public void TestCustomRegisterWithContextLoss() + { + RunTestWithRealm((realmFactory, _) => + { + BeatmapSetInfo? beatmapSetInfo = null; + + realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + + var subscription = realmFactory.Register(realm => + { + beatmapSetInfo = realm.All().First(); + + return new InvokeOnDisposal(() => beatmapSetInfo = null); + }); + + Assert.That(beatmapSetInfo, Is.Not.Null); + + using (realmFactory.BlockAllOperations()) + { + // custom disposal action fired when context lost. + Assert.That(beatmapSetInfo, Is.Null); + } + + // re-registration after context restore. + realmFactory.Run(realm => realm.Refresh()); + Assert.That(beatmapSetInfo, Is.Not.Null); + + subscription.Dispose(); + + Assert.That(beatmapSetInfo, Is.Null); + + using (realmFactory.BlockAllOperations()) + Assert.That(beatmapSetInfo, Is.Null); + + realmFactory.Run(realm => realm.Refresh()); + Assert.That(beatmapSetInfo, Is.Null); + }); + } + } +} From 5e7993c35afaae58a3dec41dddd0463b4e07a53a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 20:38:34 +0900 Subject: [PATCH 29/85] Post disposal to synchronisation context --- osu.Game/Database/RealmContextFactory.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 32f7ac99c1..26943c1951 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -260,19 +260,29 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + var syncContext = SynchronizationContext.Current; + registerSubscription(action); // This token is returned to the consumer only. // It will cause the registration to be permanently removed. return new InvokeOnDisposal(() => { - lock (contextLock) + if (ThreadSafety.IsUpdateThread) + unsubscribe(); + else + syncContext.Post(_ => unsubscribe(), null); + + void unsubscribe() { - if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + lock (contextLock) { - unsubscriptionAction?.Dispose(); - customSubscriptionActions.Remove(action); - realmSubscriptionsResetMap.Remove(action); + if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + { + unsubscriptionAction?.Dispose(); + customSubscriptionActions.Remove(action); + realmSubscriptionsResetMap.Remove(action); + } } } }); From 249f0f9697f828649c1734eeec281e0b0876ba77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 23:15:39 +0900 Subject: [PATCH 30/85] Add more lengthy comment explaining cyclic avoidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Database/RealmContextFactory.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 26943c1951..c8fa298f91 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -292,7 +292,9 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - // Get context outside of flag update to ensure beyond doubt this can't be cyclic. + // Retrieve context outside of flag update to ensure that the context is constructed, + // as attempting to access it inside the subscription if it's not constructed would lead to + // cyclic invocations of the subscription callback. var realm = Context; lock (contextLock) From deb167086212faa8d6c51fe911bcd54c1cc85c73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 23:17:33 +0900 Subject: [PATCH 31/85] Use `Array.Empty` instead of constructed list --- osu.Game/Database/EmptyRealmSet.cs | 42 +++++------------------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/osu.Game/Database/EmptyRealmSet.cs b/osu.Game/Database/EmptyRealmSet.cs index 2fecfcbe07..b7f27ba035 100644 --- a/osu.Game/Database/EmptyRealmSet.cs +++ b/osu.Game/Database/EmptyRealmSet.cs @@ -15,21 +15,14 @@ namespace osu.Game.Database { public class EmptyRealmSet : IRealmCollection { - private static List emptySet => new List(); - - public IEnumerator GetEnumerator() - { - return emptySet.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)emptySet).GetEnumerator(); - } + private IList emptySet => Array.Empty(); + public IEnumerator GetEnumerator() => emptySet.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => emptySet.GetEnumerator(); public int Count => emptySet.Count; - public T this[int index] => emptySet[index]; + public int IndexOf(object item) => emptySet.IndexOf((T)item); + public bool Contains(object item) => emptySet.Contains((T)item); public event NotifyCollectionChangedEventHandler? CollectionChanged { @@ -43,32 +36,11 @@ namespace osu.Game.Database remove => throw new NotImplementedException(); } - public int IndexOf(object item) - { - return emptySet.IndexOf((T)item); - } - - public bool Contains(object item) - { - return emptySet.Contains((T)item); - } - - public IRealmCollection Freeze() - { - throw new NotImplementedException(); - } - - public IDisposable SubscribeForNotifications(NotificationCallbackDelegate callback) - { - throw new NotImplementedException(); - } - + public IRealmCollection Freeze() => throw new NotImplementedException(); + public IDisposable SubscribeForNotifications(NotificationCallbackDelegate callback) => throw new NotImplementedException(); public bool IsValid => throw new NotImplementedException(); - public Realm Realm => throw new NotImplementedException(); - public ObjectSchema ObjectSchema => throw new NotImplementedException(); - public bool IsFrozen => throw new NotImplementedException(); } } From 351c766ea1f211b8eebe8ff58cb7d5fd0e813739 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jan 2022 23:20:03 +0900 Subject: [PATCH 32/85] Fix one remaining instance of realm query as property --- osu.Game/Overlays/MusicController.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 24d907285a..8caa69e78c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -80,9 +80,10 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } - private IQueryable availableBeatmaps => realmFactory.Context - .All() - .Where(s => !s.DeletePending); + private IQueryable queryRealmBeatmapSets() => + realmFactory.Context + .All() + .Where(s => !s.DeletePending); protected override void LoadComplete() { @@ -90,10 +91,10 @@ namespace osu.Game.Overlays // 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) + foreach (var s in queryRealmBeatmapSets()) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.Register(realm => availableBeatmaps, beatmapsChanged); + beatmapSubscription = realmFactory.Register(realm => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) From 4e5a1f27a8c35f462385dfecdeffe9e11d8c14b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 15:14:53 +0100 Subject: [PATCH 33/85] Initialise `Simple{Triple,Quadruple}` only once ever rather than create every time --- osu.Game/Beatmaps/Timing/TimeSignature.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Timing/TimeSignature.cs b/osu.Game/Beatmaps/Timing/TimeSignature.cs index 5bfeea5e9b..eebbcc34cd 100644 --- a/osu.Game/Beatmaps/Timing/TimeSignature.cs +++ b/osu.Game/Beatmaps/Timing/TimeSignature.cs @@ -27,8 +27,8 @@ namespace osu.Game.Beatmaps.Timing Numerator = numerator; } - public static TimeSignature SimpleTriple => new TimeSignature(3); - public static TimeSignature SimpleQuadruple => new TimeSignature(4); + public static TimeSignature SimpleTriple { get; } = new TimeSignature(3); + public static TimeSignature SimpleQuadruple { get; } = new TimeSignature(4); public override string ToString() => $"{Numerator}/4"; From bd748686fad63ccb40f13ae9498a53f2c3f47570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 15:20:51 +0100 Subject: [PATCH 34/85] Adjust spacing of time signature numerator input box --- osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs index 66bd341393..51b58bd3dc 100644 --- a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs +++ b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs @@ -57,8 +57,12 @@ namespace osu.Game.Screens.Edit.Timing { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Margin = new MarginPadding(10), - Text = "/4", + Margin = new MarginPadding + { + Left = 5, + Right = CONTENT_PADDING_HORIZONTAL + }, + Text = "/ 4", Font = OsuFont.Default.With(size: 20) } } From e236f5d604ee30d92fffccac17702ea4d341abc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 20:28:19 +0100 Subject: [PATCH 35/85] Add failing test coverage for correct beatmap filename generation on save --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index f89be0adf3..bf3b46c6f7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -46,6 +46,7 @@ namespace osu.Game.Tests.Visual.Editing editorBeatmap.BeatmapInfo.Metadata.Artist = "artist"; editorBeatmap.BeatmapInfo.Metadata.Title = "title"; }); + AddStep("Set author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username = "author"); AddStep("Set difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName = "difficulty"); AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); @@ -64,6 +65,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Save", () => InputManager.Keys(PlatformAction.Save)); checkMutations(); + AddAssert("Beatmap has correct .osu file path", () => editorBeatmap.BeatmapInfo.Path == "artist - title (author) [difficulty].osu"); AddStep("Exit", () => InputManager.Key(Key.Escape)); @@ -88,6 +90,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); 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"); AddAssert("Beatmap has correct difficulty name", () => editorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); } } From 838a9f69ed078e6e954ca393e1aa5d6fee9ac4b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jan 2022 19:41:41 +0100 Subject: [PATCH 36/85] Fix saved beatmap filename depending on `ToString()` implementation --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 44d6af5b73..ead86c1059 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -88,7 +88,7 @@ namespace osu.Game.Beatmaps private static string getFilename(BeatmapInfo beatmapInfo) { var metadata = beatmapInfo.Metadata; - return $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename(); + return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu".GetValidArchiveContentFilename(); } /// From ed84ae0ac0d8f57f66ee7e2360b3259e6a786e2c Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 00:42:43 +0100 Subject: [PATCH 37/85] Adjust values to Bdach's refined taste --- .../Mods/CatchModFlashlight.cs | 22 ++++++++++++------- .../Mods/ManiaModFlashlight.cs | 20 +++++++++++------ .../Mods/OsuModFlashlight.cs | 22 ++++++++++++------- .../Mods/TaikoModFlashlight.cs | 22 ++++++++++++------- osu.Game/Rulesets/Mods/ModFlashlight.cs | 13 +++++++---- 5 files changed, 64 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index f75772b04e..e8f1ebdd10 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -26,14 +26,20 @@ namespace osu.Game.Rulesets.Catch.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public override BindableNumber InitialRadius { get; } = new BindableNumber { - MinValue = 150f, - MaxValue = 600f, - Default = 350f, - Value = 350f, - Precision = 5f + MinValue = 0.4f, + MaxValue = 1.7f, + Default = 1f, + Value = 1f, + Precision = 0.1f }; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 350, + Value = 350, + }; + + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); private CatchPlayfield playfield; @@ -47,8 +53,8 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius) - : base(isRadiusBasedOnCombo, initialRadius) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { this.playfield = playfield; FlashlightSize = new Vector2(0, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index a6a3c3be73..9ca1a72584 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -28,20 +28,26 @@ namespace osu.Game.Rulesets.Mania.Mods public override BindableNumber InitialRadius { get; } = new BindableNumber { MinValue = 0f, - MaxValue = 230f, - Default = 50f, - Value = 50f, - Precision = 5f + MaxValue = 4.5f, + Default = 1f, + Value = 1f, + Precision = 0.1f }; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value); + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 50, + Value = 50, + }; + + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius) - : base(isRadiusBasedOnCombo, initialRadius) + public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index e2a6d0f0dc..c6cf5ce4b5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -40,16 +40,22 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public override BindableNumber InitialRadius { get; } = new BindableNumber { - MinValue = 90f, - MaxValue = 360f, - Default = 180f, - Value = 180f, - Precision = 5f + MinValue = 0.5f, + MaxValue = 2f, + Default = 1f, + Value = 1f, + Precision = 0.1f + }; + + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 180, + Value = 180, }; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier.Value); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -64,8 +70,8 @@ namespace osu.Game.Rulesets.Osu.Mods //public float InitialRadius { private get; set; } public bool ChangeRadius { private get; set; } - public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay) - : base(isRadiusBasedOnCombo, initialRadius) + public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { FollowDelay = followDelay; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 71c9d777ec..f235698b55 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -27,14 +27,20 @@ namespace osu.Game.Rulesets.Taiko.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public override BindableNumber InitialRadius { get; } = new BindableNumber { - MinValue = 0f, - MaxValue = 400f, - Default = 250f, - Value = 250f, - Precision = 5f + MinValue = 0, + MaxValue = 1.66f, + Default = 1f, + Value = 1f, + Precision = 0.1f }; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value); + protected override BindableNumber ModeMultiplier { get; } = new BindableNumber + { + Default = 250, + Value = 250, + }; + + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); private TaikoPlayfield playfield; @@ -49,8 +55,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; - public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius) - : base(isRadiusBasedOnCombo, initialRadius) + public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 51006d96e8..d16f310582 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public abstract BindableNumber InitialRadius { get; } + + protected abstract BindableNumber ModeMultiplier { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor @@ -100,10 +102,13 @@ namespace osu.Game.Rulesets.Mods public readonly float InitialRadius; - protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius) + public readonly float ModeMultiplier; + + protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) { IsRadiusBasedOnCombo = isRadiusBasedOnCombo; InitialRadius = initialRadius; + ModeMultiplier = modeMultiplier; } [BackgroundDependencyLoader] @@ -142,12 +147,12 @@ namespace osu.Game.Rulesets.Mods if (IsRadiusBasedOnCombo) { if (combo > 200) - return InitialRadius * 0.8f; + return InitialRadius * 0.8f * ModeMultiplier; else if (combo > 100) - return InitialRadius * 0.9f; + return InitialRadius * 0.9f * ModeMultiplier; } - return InitialRadius; + return InitialRadius * ModeMultiplier; } private Vector2 flashlightPosition; From 997c13f64367f07fcd81f0107408774d2f0fd241 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 13:36:51 +0900 Subject: [PATCH 38/85] Add locking over `realmSubscriptionsResetMap` for sanity --- osu.Game/Database/RealmContextFactory.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c8fa298f91..522a5fdfd9 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -243,9 +243,11 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); - realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); - - return Register(action); + lock (contextLock) + { + realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); + return Register(action); + } IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); } From d7a9c5fd410e370f89e83ef1bd3475a418ffe4ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 14:36:48 +0900 Subject: [PATCH 39/85] Add settings buttons to allow temporarily blocking realm access --- .../Sections/DebugSettings/MemorySettings.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 8d4fc5fc9f..f058c274e4 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; @@ -17,6 +19,9 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings [BackgroundDependencyLoader] private void load(GameHost host, RealmContextFactory realmFactory) { + SettingsButton blockAction; + SettingsButton unblockAction; + Children = new Drawable[] { new SettingsButton @@ -35,6 +40,43 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings } } }, + blockAction = new SettingsButton + { + Text = "Block realm", + }, + unblockAction = new SettingsButton + { + Text = "Unblock realm", + }, + }; + + blockAction.Action = () => + { + var blocking = realmFactory.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() + { + blocking?.Dispose(); + blocking = null; + + Scheduler.Add(() => + { + blockAction.Enabled.Value = true; + unblockAction.Action = null; + }); + } }; } } From 40aa8731900bd7f856e5f5b417aa85897bf79f0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 14:37:36 +0900 Subject: [PATCH 40/85] Rename register methods to better explain their purpose --- osu.Game.Tests/Database/GeneralUsageTests.cs | 2 +- osu.Game.Tests/Database/RealmLiveTests.cs | 2 +- .../RealmSubscriptionRegistrationTests.cs | 4 ++-- osu.Game/Database/RealmContextFactory.cs | 18 +++++++++--------- osu.Game/Database/RealmObjectExtensions.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 2 +- osu.Game/Online/BeatmapDownloadTracker.cs | 2 +- .../OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- osu.Game/Overlays/MusicController.cs | 2 +- .../Overlays/Settings/Sections/SkinSection.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 ++++---- .../Screens/Select/Carousel/TopLocalRank.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- 15 files changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index c82c1b6e59..3c62153d9e 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Database { bool callbackRan = false; - realmFactory.Register(realm => + realmFactory.RegisterCustomSubscription(realm => { var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) => { diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 0e6ad910d9..d53fcb9ac7 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -239,7 +239,7 @@ namespace osu.Game.Tests.Database { int changesTriggered = 0; - realmFactory.Register(outerRealm => + realmFactory.RegisterCustomSubscription(outerRealm => { outerRealm.All().QueryAsyncWithNotifications(gotChange); ILive? liveBeatmap = null; diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 16e9f31c7b..1799b95905 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Database { realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); - var registration = realmFactory.Register(realm => realm.All(), onChanged); + var registration = realmFactory.RegisterForNotifications(realm => realm.All(), onChanged); testEventsArriving(true); @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Database realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); - var subscription = realmFactory.Register(realm => + var subscription = realmFactory.RegisterCustomSubscription(realm => { beatmapSetInfo = realm.All().First(); diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 522a5fdfd9..697caf8cd3 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -63,6 +63,10 @@ namespace osu.Game.Database private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal(); + private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); + + private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); + private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); private readonly object contextLock = new object(); @@ -233,20 +237,16 @@ namespace osu.Game.Database } } - private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); - - private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); - - public IDisposable Register(Func> query, NotificationCallbackDelegate onChanged) + public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate onChanged) where T : RealmObjectBase { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread."); lock (contextLock) { realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); - return Register(action); + return RegisterCustomSubscription(action); } IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); @@ -257,10 +257,10 @@ namespace osu.Game.Database /// /// The work to run. Return value should be an from QueryAsyncWithNotifications, or an to clean up any bindings. /// An which should be disposed to unsubscribe any inner subscription. - public IDisposable Register(Func action) + public IDisposable RegisterCustomSubscription(Func action) { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"{nameof(Register)} must be called from the update thread."); + throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread."); var syncContext = SynchronizationContext.Current; diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index c30e1699b9..d9026d165d 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -272,7 +272,7 @@ namespace osu.Game.Database where T : RealmObjectBase { if (!RealmContextFactory.CurrentThreadSubscriptionsAllowed) - throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.Register)}"); + throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.RegisterForNotifications)}"); return collection.SubscribeForNotifications(callback); } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 4ad5693867..9cd9865441 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realmFactory.Register(realm => queryRealmKeyBindings(), (sender, changes, error) => + realmSubscription = realmFactory.RegisterForNotifications(realm => 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. diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 70599a167b..3f45afb6b2 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -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.Register(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => + realmSubscription = realmContextFactory.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 8dd28f5417..28c41bec33 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -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 = realmContextFactory.Register(realm => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realmContextFactory.RegisterForNotifications(realm => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 72e95bd6df..64ceee9fe6 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realmContextFactory.Register(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => + realmSubscription = realmContextFactory.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8caa69e78c..8df7ed9736 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -94,7 +94,7 @@ namespace osu.Game.Overlays foreach (var s in queryRealmBeatmapSets()) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.Register(realm => queryRealmBeatmapSets(), beatmapsChanged); + beatmapSubscription = realmFactory.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index c767edec71..6e5cd0da2d 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realmFactory.Register(realm => queryRealmSkins(), (sender, changes, error) => + realmSubscription = realmFactory.RegisterForNotifications(realm => 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. diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 889a6f5b79..5a6295fe74 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -190,13 +190,13 @@ namespace osu.Game.Screens.Select { base.LoadComplete(); - subscriptionSets = realmFactory.Register(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.Register(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); + subscriptionSets = realmFactory.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); + subscriptionBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.Register(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realmFactory.Register(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); + subscriptionDeletedSets = realmFactory.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index fc2188e597..c03d464ef6 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realmFactory.Register(realm => + scoreSubscription = realmFactory.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 463f878e2a..5f288b972b 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realmFactory.Register(realm => + scoreSubscription = realmFactory.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), (_, changes, ___) => diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 904648f727..4abde56ea6 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.Register( + realmSubscription = realmContextFactory.RegisterForNotifications( realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); foreach ((int id, var _) in userMap) From cb319cebdbae8cf70e4b750b1bd80ce655e41183 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 14:48:55 +0900 Subject: [PATCH 41/85] Refactor naming and add more comments to help understanding in `RealmContextFactory` subscription logic --- osu.Game/Database/RealmContextFactory.cs | 39 ++++++++++++++++-------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 697caf8cd3..0137e22e94 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -63,8 +63,22 @@ namespace osu.Game.Database private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal(); - private readonly Dictionary, IDisposable?> customSubscriptionActions = new Dictionary, IDisposable?>(); + /// + /// Holds a map of functions registered via and and a coinciding action which when triggered, + /// will unregister the subscription from realm. + /// + /// Put another way, the key is an action which registers the subscription with realm. The returned from the action is stored as the value and only + /// used internally. + /// + /// Entries in this dictionary are only removed when a consumer signals that the subscription should be permanently ceased (via their own ). + /// + private readonly Dictionary, IDisposable?> customSubscriptionsResetMap = new Dictionary, IDisposable?>(); + /// + /// Holds a map of functions registered via and a coinciding action which when triggered, + /// fires a change set event with an empty collection. This is used to inform subscribers when a realm context goes away, and ensure they don't use invalidated + /// managed realm objects from a previous firing. + /// private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); @@ -88,7 +102,7 @@ namespace osu.Game.Database Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); // Resubscribe any subscriptions - foreach (var action in customSubscriptionActions.Keys) + foreach (var action in customSubscriptionsResetMap.Keys) registerSubscription(action); } @@ -245,11 +259,12 @@ namespace osu.Game.Database lock (contextLock) { + Func action = realm => query(realm).QueryAsyncWithNotifications(onChanged); + + // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); return RegisterCustomSubscription(action); } - - IDisposable? action(Realm realm) => query(realm).QueryAsyncWithNotifications(onChanged); } /// @@ -266,8 +281,8 @@ namespace osu.Game.Database registerSubscription(action); - // This token is returned to the consumer only. - // It will cause the registration to be permanently removed. + // 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) @@ -279,10 +294,10 @@ namespace osu.Game.Database { lock (contextLock) { - if (customSubscriptionActions.TryGetValue(action, out var unsubscriptionAction)) + if (customSubscriptionsResetMap.TryGetValue(action, out var unsubscriptionAction)) { unsubscriptionAction?.Dispose(); - customSubscriptionActions.Remove(action); + customSubscriptionsResetMap.Remove(action); realmSubscriptionsResetMap.Remove(action); } } @@ -301,10 +316,10 @@ namespace osu.Game.Database lock (contextLock) { - Debug.Assert(!customSubscriptionActions.TryGetValue(action, out var found) || found == null); + Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); current_thread_subscriptions_allowed.Value = true; - customSubscriptionActions[action] = action(realm); + customSubscriptionsResetMap[action] = action(realm); current_thread_subscriptions_allowed.Value = false; } } @@ -318,10 +333,10 @@ namespace osu.Game.Database foreach (var action in realmSubscriptionsResetMap.Values) action(); - foreach (var action in customSubscriptionActions) + foreach (var action in customSubscriptionsResetMap) { action.Value?.Dispose(); - customSubscriptionActions[action.Key] = null; + customSubscriptionsResetMap[action.Key] = null; } } From 1e483ece322f4c78c4ff8eab95c1993d388a1f9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 16:40:16 +0900 Subject: [PATCH 42/85] Avoid adding "exit all screens" step when running tests interactively --- osu.Game/Tests/Visual/ScreenTestScene.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index c44a848275..b6f6ca6daa 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -48,7 +49,11 @@ namespace osu.Game.Tests.Visual public virtual void SetUpSteps() => addExitAllScreensStep(); [TearDownSteps] - public virtual void TearDownSteps() => addExitAllScreensStep(); + public virtual void TearDownSteps() + { + if (DebugUtils.IsNUnitRunning) + addExitAllScreensStep(); + } private void addExitAllScreensStep() { From e22aea0613698af3fa5870fd745764306a9273dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:05:49 +0900 Subject: [PATCH 43/85] Apply same fix to `OsuGameTestScene` --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index 6a11bd3fea..ebbd9bb5dd 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -72,8 +73,11 @@ namespace osu.Game.Tests.Visual [TearDownSteps] public void TearDownSteps() { - AddStep("exit game", () => Game.Exit()); - AddUntilStep("wait for game exit", () => Game.Parent == null); + if (DebugUtils.IsNUnitRunning) + { + AddStep("exit game", () => Game.Exit()); + AddUntilStep("wait for game exit", () => Game.Parent == null); + } } protected void CreateGame() From 161a2a321ef5ecc93dfeb9d5a99af66f7881be2a Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 09:07:07 +0100 Subject: [PATCH 44/85] Remove bindable from ModeMultiplier --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 8 ++------ osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 8 ++------ osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 8 ++------ osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 8 ++------ osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 +- 5 files changed, 9 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e8f1ebdd10..4fbbf63abf 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -33,13 +33,9 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 350, - Value = 350, - }; + protected override float ModeMultiplier => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); private CatchPlayfield playfield; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 9ca1a72584..61f73a1ee5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -34,13 +34,9 @@ namespace osu.Game.Rulesets.Mania.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 50, - Value = 50, - }; + protected override float ModeMultiplier => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); private class ManiaFlashlight : Flashlight { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index c6cf5ce4b5..75299863a9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -47,15 +47,11 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 180, - Value = 180, - }; + protected override float ModeMultiplier => 180; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index f235698b55..65a173b491 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -34,13 +34,9 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.1f }; - protected override BindableNumber ModeMultiplier { get; } = new BindableNumber - { - Default = 250, - Value = 250, - }; + protected override float ModeMultiplier => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier.Value); + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); private TaikoPlayfield playfield; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index d16f310582..7b980e8097 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public abstract BindableNumber InitialRadius { get; } - protected abstract BindableNumber ModeMultiplier { get; } + protected abstract float ModeMultiplier { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From 52cd906af6552ecb099d40454d10e2e266a4e92f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:45:31 +0900 Subject: [PATCH 45/85] Move context retrieval inside lock --- osu.Game/Database/RealmContextFactory.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 0137e22e94..be484329bc 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -309,13 +309,13 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - // Retrieve context outside of flag update to ensure that the context is constructed, - // as attempting to access it inside the subscription if it's not constructed would lead to - // cyclic invocations of the subscription callback. - var realm = Context; - lock (contextLock) { + // Retrieve context outside of flag update to ensure that the context is constructed, + // as attempting to access it inside the subscription if it's not constructed would lead to + // cyclic invocations of the subscription callback. + var realm = Context; + Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); current_thread_subscriptions_allowed.Value = true; From abf14f09820bb8f5859604135174f36f0e9e7cc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:46:53 +0900 Subject: [PATCH 46/85] Lock unregistration for sanity --- osu.Game/Database/RealmContextFactory.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index be484329bc..5dd4e88ccd 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -330,13 +330,16 @@ namespace osu.Game.Database /// private void unregisterAllSubscriptions() { - foreach (var action in realmSubscriptionsResetMap.Values) - action(); - - foreach (var action in customSubscriptionsResetMap) + lock (contextLock) { - action.Value?.Dispose(); - customSubscriptionsResetMap[action.Key] = null; + foreach (var action in realmSubscriptionsResetMap.Values) + action(); + + foreach (var action in customSubscriptionsResetMap) + { + action.Value?.Dispose(); + customSubscriptionsResetMap[action.Key] = null; + } } } From f4e7211ef1356f6598f4ad6038d896c771f39cbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:52:36 +0900 Subject: [PATCH 47/85] Add xmldoc for `RegisterForNotifications` --- osu.Game/Database/RealmContextFactory.cs | 27 +++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 5dd4e88ccd..f8470b8c73 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -251,7 +251,28 @@ namespace osu.Game.Database } } - public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate onChanged) + /// + /// Subscribe to a realm collection and begin watching for asynchronous changes. + /// + /// + /// This adds osu! specific thread and managed state safety checks on top of . + /// + /// In addition to the documented realm behaviour, we have the additional requirement of handling subscriptions over potential context loss. + /// When this happens, callback events will be automatically fired: + /// - On context loss, a callback with an empty collection and null will be invoked. + /// - On context revival, a standard initial realm callback will arrive, with null and an up-to-date collection. + /// + /// The to observe for changes. + /// Type of the elements in the list. + /// + /// The callback to be invoked with the updated . + /// + /// A subscription token. It must be kept alive for as long as you want to receive change notifications. + /// To stop receiving notifications, call . + /// + /// May be null in the case the provided collection is not managed. + /// + public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate callback) where T : RealmObjectBase { if (!ThreadSafety.IsUpdateThread) @@ -259,10 +280,10 @@ namespace osu.Game.Database lock (contextLock) { - Func action = realm => query(realm).QueryAsyncWithNotifications(onChanged); + Func action = realm => query(realm).QueryAsyncWithNotifications(callback); // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - realmSubscriptionsResetMap.Add(action, () => onChanged(new EmptyRealmSet(), null, null)); + realmSubscriptionsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); return RegisterCustomSubscription(action); } } From bf5bf8d1fd147f1a0e27756321512eaa0676e3d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:58:53 +0900 Subject: [PATCH 48/85] Rename dictionaries to match methods --- osu.Game/Database/RealmContextFactory.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index f8470b8c73..738d0a70a9 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -79,7 +79,7 @@ namespace osu.Game.Database /// fires a change set event with an empty collection. This is used to inform subscribers when a realm context goes away, and ensure they don't use invalidated /// managed realm objects from a previous firing. /// - private readonly Dictionary, Action> realmSubscriptionsResetMap = new Dictionary, Action>(); + private readonly Dictionary, Action> notificationsResetMap = new Dictionary, Action>(); private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); @@ -283,7 +283,7 @@ namespace osu.Game.Database Func action = realm => query(realm).QueryAsyncWithNotifications(callback); // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - realmSubscriptionsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); + notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); return RegisterCustomSubscription(action); } } @@ -319,7 +319,7 @@ namespace osu.Game.Database { unsubscriptionAction?.Dispose(); customSubscriptionsResetMap.Remove(action); - realmSubscriptionsResetMap.Remove(action); + notificationsResetMap.Remove(action); } } } @@ -353,7 +353,7 @@ namespace osu.Game.Database { lock (contextLock) { - foreach (var action in realmSubscriptionsResetMap.Values) + foreach (var action in notificationsResetMap.Values) action(); foreach (var action in customSubscriptionsResetMap) From e3083c2477ae80b23f5c17c9360c7aa51b07dc88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:05:30 +0900 Subject: [PATCH 49/85] Fix copy pasted xmldoc --- osu.Game/Database/RealmContextFactory.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 738d0a70a9..3956738147 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -264,14 +264,12 @@ namespace osu.Game.Database /// /// The to observe for changes. /// Type of the elements in the list. - /// /// The callback to be invoked with the updated . /// /// A subscription token. It must be kept alive for as long as you want to receive change notifications. /// To stop receiving notifications, call . - /// - /// May be null in the case the provided collection is not managed. /// + /// public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate callback) where T : RealmObjectBase { From b0919722ac1c77fb484a7175e3f90cf325d03f9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:24:25 +0900 Subject: [PATCH 50/85] Guard against potential exception while blocking realm --- .../Sections/DebugSettings/MemorySettings.cs | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index f058c274e4..c5854981e6 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.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; @@ -52,30 +54,38 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings blockAction.Action = () => { - var blocking = realmFactory.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(() => + try { - Thread.Sleep(10000); - unblock(); - }); + var token = realmFactory.BlockAllOperations(); - unblockAction.Action = unblock; + blockAction.Enabled.Value = false; - void unblock() - { - blocking?.Dispose(); - blocking = null; - - Scheduler.Add(() => + // 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(() => { - blockAction.Enabled.Value = true; - unblockAction.Action = null; + 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"); } }; } From 9afa034296e8f06642c907d44ab3061c076c9815 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:36:16 +0900 Subject: [PATCH 51/85] Fix attempt to revive update thread realm context from non-update thread --- osu.Game/Database/RealmContextFactory.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 3956738147..778092c543 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -594,7 +594,7 @@ namespace osu.Game.Database if (isDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); - SynchronizationContext syncContext; + SynchronizationContext? syncContext = null; try { @@ -602,10 +602,20 @@ namespace osu.Game.Database lock (contextLock) { - if (!ThreadSafety.IsUpdateThread && context != null) - throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + if (context == null) + { + // null context means the update thread has not yet retrieved its context. + // we don't need to worry about reviving the update context 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; + } - syncContext = SynchronizationContext.Current; unregisterAllSubscriptions(); Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); From 66c5d77d6388686c733eb229e822d0ad4120dee1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 18:55:15 +0900 Subject: [PATCH 52/85] Allow realm migration to run again if interrupted halfway --- osu.Game/Database/EFToRealmMigrator.cs | 258 ++++++++++++------------- osu.Game/Screens/Loader.cs | 15 +- 2 files changed, 140 insertions(+), 133 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index bbbdac352e..edce99e302 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -27,7 +27,9 @@ namespace osu.Game.Database { internal class EFToRealmMigrator : CompositeDrawable { - public bool FinishedMigrating { get; private set; } + public Task MigrationCompleted => migrationCompleted.Task; + + private readonly TaskCompletionSource migrationCompleted = new TaskCompletionSource(); [Resolved] private DatabaseContextFactory efContextFactory { get; set; } = null!; @@ -99,6 +101,17 @@ namespace osu.Game.Database { using (var ef = efContextFactory.Get()) { + realmContextFactory.Write(realm => + { + // 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. + realm.RemoveAll(); + realm.RemoveAll(); + realm.RemoveAll(); + realm.RemoveAll(); + }); + migrateSettings(ef); migrateSkins(ef); migrateBeatmaps(ef); @@ -114,7 +127,7 @@ namespace osu.Game.Database Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important); }, TaskCreationOptions.LongRunning).ContinueWith(t => { - FinishedMigrating = true; + migrationCompleted.SetResult(true); }); } @@ -149,87 +162,78 @@ namespace osu.Game.Database { log($"Found {count} beatmaps in EF"); - // only migrate data if the realm database is empty. - // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. - if (realm.All().Any(s => !s.Protected)) - { - log("Skipping migration as realm already has beatmaps loaded"); - } - else - { - var transaction = realm.BeginWrite(); - int written = 0; + var transaction = realm.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 = realm.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, realm, realmBeatmapSet); + + foreach (var beatmap in beatmapSet.Beatmaps) + { + var ruleset = realm.Find(beatmap.RulesetInfo.ShortName); + var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); + + var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) { - OnlineID = beatmapSet.OnlineID ?? -1, - DateAdded = beatmapSet.DateAdded, - Status = beatmapSet.Status, - DeletePending = beatmapSet.DeletePending, - Hash = beatmapSet.Hash, - Protected = beatmapSet.Protected, + DifficultyName = beatmap.DifficultyName, + Status = beatmap.Status, + OnlineID = beatmap.OnlineID ?? -1, + Length = beatmap.Length, + BPM = beatmap.BPM, + Hash = beatmap.Hash, + StarRating = beatmap.StarRating, + MD5Hash = beatmap.MD5Hash, + Hidden = beatmap.Hidden, + AudioLeadIn = beatmap.AudioLeadIn, + StackLeniency = beatmap.StackLeniency, + SpecialStyle = beatmap.SpecialStyle, + LetterboxInBreaks = beatmap.LetterboxInBreaks, + WidescreenStoryboard = beatmap.WidescreenStoryboard, + EpilepsyWarning = beatmap.EpilepsyWarning, + SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate, + DistanceSpacing = beatmap.DistanceSpacing, + BeatDivisor = beatmap.BeatDivisor, + GridSize = beatmap.GridSize, + TimelineZoom = beatmap.TimelineZoom, + Countdown = beatmap.Countdown, + CountdownOffset = beatmap.CountdownOffset, + MaxCombo = beatmap.MaxCombo, + Bookmarks = beatmap.Bookmarks, + BeatmapSet = realmBeatmapSet, }; - migrateFiles(beatmapSet, realm, realmBeatmapSet); - - foreach (var beatmap in beatmapSet.Beatmaps) - { - var ruleset = realm.Find(beatmap.RulesetInfo.ShortName); - var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); - - var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) - { - DifficultyName = beatmap.DifficultyName, - Status = beatmap.Status, - OnlineID = beatmap.OnlineID ?? -1, - Length = beatmap.Length, - BPM = beatmap.BPM, - Hash = beatmap.Hash, - StarRating = beatmap.StarRating, - MD5Hash = beatmap.MD5Hash, - Hidden = beatmap.Hidden, - AudioLeadIn = beatmap.AudioLeadIn, - StackLeniency = beatmap.StackLeniency, - SpecialStyle = beatmap.SpecialStyle, - LetterboxInBreaks = beatmap.LetterboxInBreaks, - WidescreenStoryboard = beatmap.WidescreenStoryboard, - EpilepsyWarning = beatmap.EpilepsyWarning, - SamplesMatchPlaybackRate = beatmap.SamplesMatchPlaybackRate, - DistanceSpacing = beatmap.DistanceSpacing, - BeatDivisor = beatmap.BeatDivisor, - GridSize = beatmap.GridSize, - TimelineZoom = beatmap.TimelineZoom, - Countdown = beatmap.Countdown, - CountdownOffset = beatmap.CountdownOffset, - MaxCombo = beatmap.MaxCombo, - Bookmarks = beatmap.Bookmarks, - BeatmapSet = realmBeatmapSet, - }; - - realmBeatmapSet.Beatmaps.Add(realmBeatmap); - } - - realm.Add(realmBeatmapSet); + realmBeatmapSet.Beatmaps.Add(realmBeatmap); } - } - finally - { - transaction.Commit(); - } - log($"Successfully migrated {count} beatmaps to realm"); + realm.Add(realmBeatmapSet); + } } + finally + { + transaction.Commit(); + } + + log($"Successfully migrated {count} beatmaps to realm"); }); } @@ -280,70 +284,62 @@ namespace osu.Game.Database { log($"Found {count} scores in EF"); - // only migrate data if the realm database is empty. - if (realm.All().Any()) - { - log("Skipping migration as realm already has scores loaded"); - } - else - { - var transaction = realm.BeginWrite(); - int written = 0; + var transaction = realm.BeginWrite(); + int written = 0; - try + try + { + foreach (var score in existingScores) { - foreach (var score in existingScores) + if (++written % 1000 == 0) { - if (++written % 1000 == 0) - { - transaction.Commit(); - transaction = realm.BeginWrite(); - log($"Migrated {written}/{count} scores..."); - } - - var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); - var ruleset = realm.Find(score.Ruleset.ShortName); - var user = new RealmUser - { - OnlineID = score.User.OnlineID, - Username = score.User.Username - }; - - var realmScore = new ScoreInfo(beatmap, ruleset, user) - { - Hash = score.Hash, - DeletePending = score.DeletePending, - OnlineID = score.OnlineID ?? -1, - ModsJson = score.ModsJson, - StatisticsJson = score.StatisticsJson, - TotalScore = score.TotalScore, - MaxCombo = score.MaxCombo, - Accuracy = score.Accuracy, - HasReplay = ((IScoreInfo)score).HasReplay, - Date = score.Date, - PP = score.PP, - Rank = score.Rank, - HitEvents = score.HitEvents, - Passed = score.Passed, - Combo = score.Combo, - Position = score.Position, - Statistics = score.Statistics, - Mods = score.Mods, - APIMods = score.APIMods, - }; - - migrateFiles(score, realm, realmScore); - - realm.Add(realmScore); + transaction.Commit(); + transaction = realm.BeginWrite(); + log($"Migrated {written}/{count} scores..."); } - } - finally - { - transaction.Commit(); - } - log($"Successfully migrated {count} scores to realm"); + var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); + var ruleset = realm.Find(score.Ruleset.ShortName); + var user = new RealmUser + { + OnlineID = score.User.OnlineID, + Username = score.User.Username + }; + + var realmScore = new ScoreInfo(beatmap, ruleset, user) + { + Hash = score.Hash, + DeletePending = score.DeletePending, + OnlineID = score.OnlineID ?? -1, + ModsJson = score.ModsJson, + StatisticsJson = score.StatisticsJson, + TotalScore = score.TotalScore, + MaxCombo = score.MaxCombo, + Accuracy = score.Accuracy, + HasReplay = ((IScoreInfo)score).HasReplay, + Date = score.Date, + PP = score.PP, + Rank = score.Rank, + HitEvents = score.HitEvents, + Passed = score.Passed, + Combo = score.Combo, + Position = score.Position, + Statistics = score.Statistics, + Mods = score.Mods, + APIMods = score.APIMods, + }; + + migrateFiles(score, realm, realmScore); + + realm.Add(realmScore); + } } + finally + { + transaction.Commit(); + } + + log($"Successfully migrated {count} scores to realm"); }); } diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 8c4a13f2bd..a72ba89dfa 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -74,11 +74,22 @@ namespace osu.Game.Screens base.OnEntering(last); LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal); - LoadComponentAsync(loadableScreen = CreateLoadableScreen()); // A non-null context factory means there's still content to migrate. if (efContextFactory != null) + { LoadComponentAsync(realmMigrator = new EFToRealmMigrator(), AddInternal); + realmMigrator.MigrationCompleted.ContinueWith(_ => Schedule(() => + { + // Delay initial screen loading to ensure that the migration is in a complete and sane state + // before the intro screen may import the game intro beatmap. + LoadComponentAsync(loadableScreen = CreateLoadableScreen()); + })); + } + else + { + LoadComponentAsync(loadableScreen = CreateLoadableScreen()); + } LoadComponentAsync(spinner = new LoadingSpinner(true, true) { @@ -96,7 +107,7 @@ namespace osu.Game.Screens private void checkIfLoaded() { - if (loadableScreen.LoadState != LoadState.Ready || !precompiler.FinishedCompiling || realmMigrator?.FinishedMigrating == false) + if (loadableScreen?.LoadState != LoadState.Ready || !precompiler.FinishedCompiling) { Schedule(checkIfLoaded); return; From 948867898cb36ed2a32ff2e58a6f7c9de64d66d5 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 11:38:52 +0100 Subject: [PATCH 53/85] ModeMultiplier rename --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 8 ++++---- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 8 ++++---- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 8 ++++---- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 8 ++++---- osu.Game/Rulesets/Mods/ModFlashlight.cs | 14 ++++++-------- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 4fbbf63abf..d48382a9ee 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 350; + protected virtual float DefaultFlashlightSize => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); private CatchPlayfield playfield; @@ -49,8 +49,8 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { this.playfield = playfield; FlashlightSize = new Vector2(0, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 61f73a1ee5..eb3f60edce 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -34,16 +34,16 @@ namespace osu.Game.Rulesets.Mania.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 50; + protected virtual float DefaultFlashlightSize => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 75299863a9..6a9d199c54 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -47,11 +47,11 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 180; + protected virtual float DefaultFlashlightSize => 180; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, DefaultFlashlightSize); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -66,8 +66,8 @@ namespace osu.Game.Rulesets.Osu.Mods //public float InitialRadius { private get; set; } public bool ChangeRadius { private get; set; } - public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { FollowDelay = followDelay; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 65a173b491..8de7c859c4 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.1f }; - protected override float ModeMultiplier => 250; + protected virtual float DefaultFlashlightSize => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, ModeMultiplier); + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); private TaikoPlayfield playfield; @@ -51,8 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; - public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) - : base(isRadiusBasedOnCombo, initialRadius, modeMultiplier) + public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 7b980e8097..531ee92b7a 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -38,8 +38,6 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial radius", "Initial radius of the flashlight area.")] public abstract BindableNumber InitialRadius { get; } - - protected abstract float ModeMultiplier { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor @@ -102,13 +100,13 @@ namespace osu.Game.Rulesets.Mods public readonly float InitialRadius; - public readonly float ModeMultiplier; + public readonly float DefaultFlashlightSize; - protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float modeMultiplier) + protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) { IsRadiusBasedOnCombo = isRadiusBasedOnCombo; InitialRadius = initialRadius; - ModeMultiplier = modeMultiplier; + DefaultFlashlightSize = defaultFlashlightSize; } [BackgroundDependencyLoader] @@ -147,12 +145,12 @@ namespace osu.Game.Rulesets.Mods if (IsRadiusBasedOnCombo) { if (combo > 200) - return InitialRadius * 0.8f * ModeMultiplier; + return InitialRadius * 0.8f * DefaultFlashlightSize; else if (combo > 100) - return InitialRadius * 0.9f * ModeMultiplier; + return InitialRadius * 0.9f * DefaultFlashlightSize; } - return InitialRadius * ModeMultiplier; + return InitialRadius * DefaultFlashlightSize; } private Vector2 flashlightPosition; From deaff340d2733c544b8e87d78a51d5d69063ee27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 17:58:06 +0900 Subject: [PATCH 54/85] Add test coverage of saving velocity --- .../Editor/TestSceneSliderVelocityAdjust.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs new file mode 100644 index 0000000000..4750c97566 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Input; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Screens.Edit.Timing; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public class TestSceneSliderVelocityAdjust : OsuGameTestScene + { + private Screens.Edit.Editor editor => Game.ScreenStack.CurrentScreen as Screens.Edit.Editor; + + private EditorBeatmap editorBeatmap => editor.ChildrenOfType().FirstOrDefault(); + + private EditorClock editorClock => editor.ChildrenOfType().FirstOrDefault(); + + private Slider slider => editorBeatmap.HitObjects.OfType().FirstOrDefault(); + + private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault(); + + private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType().First(); + + private IndeterminateSliderWithTextBoxInput velocityTextBox => Game.ChildrenOfType().First().ChildrenOfType>().First(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + private bool editorComponentsReady => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && editor?.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + + [TestCase(true)] + [TestCase(false)] + public void TestVelocityChangeSavesCorrectly(bool adjustVelocity) + { + double? velocity = null; + + AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader())); + AddUntilStep("wait for editor load", () => editorComponentsReady); + + AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time)); + AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3)); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre)); + AddStep("start placement", () => InputManager.Click(MouseButton.Left)); + + AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType().First().ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); + AddStep("end placement", () => InputManager.Click(MouseButton.Right)); + + AddStep("exit placement mode", () => InputManager.Key(Key.Number1)); + + AddAssert("slider placed", () => slider != null); + + AddStep("select slider", () => editorBeatmap.SelectedHitObjects.Add(slider)); + + AddAssert("ensure one slider placed", () => slider != null); + + AddStep("store velocity", () => velocity = slider.Velocity); + + if (adjustVelocity) + { + AddStep("open velocity adjust panel", () => difficultyPointPiece.TriggerClick()); + AddStep("change velocity", () => velocityTextBox.Current.Value = 2); + + AddAssert("velocity adjusted", () => + { + Debug.Assert(velocity != null); + return Precision.AlmostEquals(velocity.Value * 2, slider.Velocity); + }); + + AddStep("store velocity", () => velocity = slider.Velocity); + } + + AddStep("save", () => InputManager.Keys(PlatformAction.Save)); + AddStep("exit", () => InputManager.Key(Key.Escape)); + + AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader())); + AddUntilStep("wait for editor load", () => editorComponentsReady); + + AddStep("seek to slider", () => editorClock.Seek(slider.StartTime)); + AddAssert("slider has correct velocity", () => slider.Velocity == velocity); + } + } +} From c3758047fd9e2113173ea9bfcba50a575f5b7035 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 19:54:07 +0900 Subject: [PATCH 55/85] Don't include nested hit objects' `DifficultyControLPoints` in legacy encoder logic The editor doesn't currently propagate velocity to nested objects. We're not yet sure whether it should or not. For now, let's just ignore nested objects' `DifficultyControlPoints` for simplicity. Note that this only affects osu! ruleset due to the pre-check on `isOsuRuleset`. --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 4cf6d3335f..9d848fd8a4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -242,12 +242,7 @@ namespace osu.Game.Beatmaps.Formats yield break; foreach (var hitObject in hitObjects) - { yield return hitObject.DifficultyControlPoint; - - foreach (var nested in collectDifficultyControlPoints(hitObject.NestedHitObjects)) - yield return nested; - } } void extractDifficultyControlPoints(IEnumerable hitObjects) From 6eb2c28e41369be24fbefb14478b62935207992d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 19:59:58 +0900 Subject: [PATCH 56/85] Rename `RealmContextFactory` to `RealmAccess` --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 20 +- .../Beatmaps/IO/BeatmapImportHelper.cs | 4 +- .../Database/BeatmapImporterTests.cs | 258 +++++++++--------- osu.Game.Tests/Database/FileStoreTests.cs | 24 +- osu.Game.Tests/Database/GeneralUsageTests.cs | 22 +- osu.Game.Tests/Database/RealmLiveTests.cs | 60 ++-- .../RealmSubscriptionRegistrationTests.cs | 42 +-- osu.Game.Tests/Database/RealmTest.cs | 34 +-- osu.Game.Tests/Database/RulesetStoreTests.cs | 18 +- .../Database/TestRealmKeyBindingStore.cs | 16 +- .../Gameplay/TestSceneStoryboardSamples.cs | 2 +- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 20 +- .../Background/TestSceneUserDimBackgrounds.cs | 6 +- .../TestSceneManageCollectionsDialog.cs | 6 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 6 +- .../TestSceneDrawableRoomPlaylist.cs | 6 +- .../Multiplayer/TestSceneMultiplayer.cs | 6 +- .../TestSceneMultiplayerMatchSongSelect.cs | 6 +- .../TestSceneMultiplayerMatchSubScreen.cs | 6 +- .../TestSceneMultiplayerPlaylist.cs | 6 +- .../TestSceneMultiplayerQueueList.cs | 6 +- .../TestSceneMultiplayerReadyButton.cs | 6 +- .../TestSceneMultiplayerSpectateButton.cs | 6 +- .../TestScenePlaylistsSongSelect.cs | 6 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 6 +- .../TestSceneChangeAndUseGameplayBindings.cs | 2 +- .../TestScenePlaylistsRoomCreation.cs | 6 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 4 +- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 8 +- .../SongSelect/TestSceneFilterControl.cs | 6 +- .../SongSelect/TestScenePlaySongSelect.cs | 6 +- .../SongSelect/TestSceneTopLocalRank.cs | 8 +- .../TestSceneDeleteLocalScore.cs | 12 +- osu.Game/Beatmaps/BeatmapManager.cs | 30 +- osu.Game/Beatmaps/BeatmapModelManager.cs | 8 +- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 2 +- osu.Game/Configuration/SettingsStore.cs | 6 +- osu.Game/Database/EFToRealmMigrator.cs | 12 +- ...{RealmContextFactory.cs => RealmAccess.cs} | 10 +- osu.Game/Database/RealmLive.cs | 14 +- osu.Game/Database/RealmObjectExtensions.cs | 12 +- osu.Game/IO/IStorageResourceProvider.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 8 +- osu.Game/Input/RealmKeyBindingStore.cs | 10 +- osu.Game/Online/BeatmapDownloadTracker.cs | 4 +- .../OnlinePlayBeatmapAvailabilityTracker.cs | 10 +- osu.Game/Online/ScoreDownloadTracker.cs | 4 +- osu.Game/OsuGameBase.cs | 22 +- osu.Game/Overlays/MusicController.cs | 10 +- .../Sections/DebugSettings/MemorySettings.cs | 6 +- .../Settings/Sections/Input/KeyBindingRow.cs | 4 +- .../Sections/Input/KeyBindingsSubsection.cs | 8 +- .../Overlays/Settings/Sections/SkinSection.cs | 14 +- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 4 +- .../Configuration/RulesetConfigManager.cs | 12 +- osu.Game/Rulesets/RulesetConfigCache.cs | 8 +- osu.Game/Rulesets/RulesetStore.cs | 8 +- osu.Game/Scoring/ScoreManager.cs | 12 +- osu.Game/Scoring/ScoreModelManager.cs | 6 +- osu.Game/Screens/Menu/IntroScreen.cs | 6 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 14 +- .../Screens/Select/Carousel/TopLocalRank.cs | 4 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 6 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 4 +- osu.Game/Skinning/Skin.cs | 4 +- osu.Game/Skinning/SkinManager.cs | 22 +- osu.Game/Skinning/SkinModelManager.cs | 6 +- osu.Game/Stores/BeatmapImporter.cs | 6 +- osu.Game/Stores/RealmArchiveModelImporter.cs | 16 +- osu.Game/Stores/RealmArchiveModelManager.cs | 12 +- osu.Game/Stores/RealmFileStore.cs | 8 +- .../Drawables/DrawableStoryboard.cs | 4 +- .../Tests/Beatmaps/HitObjectSampleTest.cs | 2 +- osu.Game/Tests/Visual/EditorTestScene.cs | 14 +- osu.Game/Tests/Visual/OsuTestScene.cs | 6 +- osu.Game/Tests/Visual/SkinnableTestScene.cs | 2 +- 76 files changed, 516 insertions(+), 516 deletions(-) rename osu.Game/Database/{RealmContextFactory.cs => RealmAccess.cs} (98%) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index bb22fab51c..412f86bd1c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -16,7 +16,7 @@ namespace osu.Game.Benchmarks public class BenchmarkRealmReads : BenchmarkTest { private TemporaryNativeStorage storage; - private RealmContextFactory realmFactory; + private RealmAccess realm; private UpdateThread updateThread; [Params(1, 100, 1000)] @@ -27,9 +27,9 @@ namespace osu.Game.Benchmarks storage = new TemporaryNativeStorage("realm-benchmark"); storage.DeleteDirectory(string.Empty); - realmFactory = new RealmContextFactory(storage, "client"); + realm = new RealmAccess(storage, "client"); - realmFactory.Run(realm => + realm.Run(realm => { realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); }); @@ -41,7 +41,7 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDirectPropertyRead() { - realmFactory.Run(realm => + realm.Run(realm => { var beatmapSet = realm.All().First(); @@ -61,7 +61,7 @@ namespace osu.Game.Benchmarks { try { - var beatmapSet = realmFactory.Context.All().First(); + var beatmapSet = realm.Realm.All().First(); for (int i = 0; i < ReadsPerFetch; i++) { @@ -80,9 +80,9 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkRealmLivePropertyRead() { - realmFactory.Run(realm => + realm.Run(r => { - var beatmapSet = realm.All().First().ToLive(realmFactory); + var beatmapSet = r.All().First().ToLive(realm); for (int i = 0; i < ReadsPerFetch; i++) { @@ -100,7 +100,7 @@ namespace osu.Game.Benchmarks { try { - var beatmapSet = realmFactory.Context.All().First().ToLive(realmFactory); + var beatmapSet = realm.Realm.All().First().ToLive(realm); for (int i = 0; i < ReadsPerFetch; i++) { @@ -119,7 +119,7 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDetachedPropertyRead() { - realmFactory.Run(realm => + realm.Run(realm => { var beatmapSet = realm.All().First().Detach(); @@ -133,7 +133,7 @@ namespace osu.Game.Benchmarks [GlobalCleanup] public void Cleanup() { - realmFactory?.Dispose(); + realm?.Dispose(); storage?.Dispose(); updateThread?.Exit(); } diff --git a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs index 7aa2dc7093..9e440c6bce 100644 --- a/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs +++ b/osu.Game.Tests/Beatmaps/IO/BeatmapImportHelper.cs @@ -53,9 +53,9 @@ namespace osu.Game.Tests.Beatmaps.IO private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) { - var realmContextFactory = osu.Dependencies.Get(); + var realm = osu.Dependencies.Get(); - realmContextFactory.Run(realm => BeatmapImporterTests.EnsureLoaded(realm, timeout)); + realm.Run(r => BeatmapImporterTests.EnsureLoaded(r, timeout)); // TODO: add back some extra checks outside of the realm ones? // var set = queryBeatmapSets().First(); diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 227314cffd..a52b21244c 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -38,10 +38,10 @@ namespace osu.Game.Tests.Database [Test] public void TestDetachBeatmapSet() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapModelManager(realmFactory, storage)) - using (new RulesetStore(realmFactory, storage)) + using (var importer = new BeatmapModelManager(realm, storage)) + using (new RulesetStore(realm, storage)) { ILive? beatmapSet; @@ -82,10 +82,10 @@ namespace osu.Game.Tests.Database [Test] public void TestUpdateDetachedBeatmapSet() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapModelManager(realmFactory, storage)) - using (new RulesetStore(realmFactory, storage)) + using (var importer = new BeatmapModelManager(realm, storage)) + using (new RulesetStore(realm, storage)) { ILive? beatmapSet; @@ -139,53 +139,53 @@ namespace osu.Game.Tests.Database [Test] public void TestImportBeatmapThenCleanup() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapModelManager(realmFactory, storage)) - using (new RulesetStore(realmFactory, storage)) + using (var importer = new BeatmapModelManager(realm, storage)) + using (new RulesetStore(realm, storage)) { ILive? imported; using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) imported = await importer.Import(reader); - Assert.AreEqual(1, realmFactory.Context.All().Count()); + Assert.AreEqual(1, realm.Realm.All().Count()); Assert.NotNull(imported); Debug.Assert(imported != null); imported.PerformWrite(s => s.DeletePending = true); - Assert.AreEqual(1, realmFactory.Context.All().Count(s => s.DeletePending)); + Assert.AreEqual(1, realm.Realm.All().Count(s => s.DeletePending)); } }); Logger.Log("Running with no work to purge pending deletions"); - RunTestWithRealm((realmFactory, _) => { Assert.AreEqual(0, realmFactory.Context.All().Count()); }); + RunTestWithRealm((realm, _) => { Assert.AreEqual(0, realm.Realm.All().Count()); }); } [Test] public void TestImportWhenClosed() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - await LoadOszIntoStore(importer, realmFactory.Context); + await LoadOszIntoStore(importer, realm.Realm); }); } [Test] public void TestAccessFileAfterImport() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); var beatmap = imported.Beatmaps.First(); var file = beatmap.File; @@ -198,24 +198,24 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenDelete() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); }); } [Test] public void TestImportThenDeleteFromStream() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? tempPath = TestResources.GetTestBeatmapForImport(); @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Database using (var stream = File.OpenRead(tempPath)) { importedSet = await importer.Import(new ImportTask(stream, Path.GetFileName(tempPath))); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); } Assert.NotNull(importedSet); @@ -233,39 +233,39 @@ namespace osu.Game.Tests.Database Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing"); File.Delete(tempPath); - var imported = realmFactory.Context.All().First(beatmapSet => beatmapSet.ID == importedSet.ID); + var imported = realm.Realm.All().First(beatmapSet => beatmapSet.ID == importedSet.ID); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); }); } [Test] public void TestImportThenImport() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); - checkBeatmapSetCount(realmFactory.Context, 1); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkBeatmapSetCount(realm.Realm, 1); + checkSingleReferencedFileCount(realm.Realm, 18); }); } [Test] public void TestImportThenImportWithReZip() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -274,7 +274,7 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); string hashBefore = hashFile(temp); @@ -292,7 +292,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -311,10 +311,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenImportWithChangedHashedFile() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -323,9 +323,9 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - await createScoreForBeatmap(realmFactory.Context, imported.Beatmaps.First()); + await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First()); using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); @@ -343,7 +343,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); // check the newly "imported" beatmap is not the original. Assert.NotNull(importedSecondTime); @@ -363,10 +363,10 @@ namespace osu.Game.Tests.Database [Ignore("intentionally broken by import optimisations")] public void TestImportThenImportWithChangedFile() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -375,7 +375,7 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); @@ -392,7 +392,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -411,10 +411,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenImportWithDifferentFilename() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -423,7 +423,7 @@ namespace osu.Game.Tests.Database try { - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); @@ -440,7 +440,7 @@ namespace osu.Game.Tests.Database var importedSecondTime = await importer.Import(new ImportTask(temp)); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); Assert.NotNull(importedSecondTime); Debug.Assert(importedSecondTime != null); @@ -460,12 +460,12 @@ namespace osu.Game.Tests.Database [Ignore("intentionally broken by import optimisations")] public void TestImportCorruptThenImport() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); var firstFile = imported.Files.First(); @@ -476,7 +476,7 @@ namespace osu.Game.Tests.Database using (var stream = storage.GetStream(firstFile.File.GetStoragePath(), FileAccess.Write, FileMode.Create)) stream.WriteByte(0); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); using (var stream = storage.GetStream(firstFile.File.GetStoragePath())) Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import"); @@ -485,18 +485,18 @@ namespace osu.Game.Tests.Database Assert.IsTrue(imported.ID == importedSecondTime.ID); Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); - checkBeatmapSetCount(realmFactory.Context, 1); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkBeatmapSetCount(realm.Realm, 1); + checkSingleReferencedFileCount(realm.Realm, 18); }); } [Test] public void TestModelCreationFailureDoesntReturn() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); var progressNotification = new ImportProgressNotification(); @@ -510,8 +510,8 @@ namespace osu.Game.Tests.Database new ImportTask(zipStream, string.Empty) ); - checkBeatmapSetCount(realmFactory.Context, 0); - checkBeatmapCount(realmFactory.Context, 0); + checkBeatmapSetCount(realm.Realm, 0); + checkBeatmapCount(realm.Realm, 0); Assert.IsEmpty(imported); Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State); @@ -521,7 +521,7 @@ namespace osu.Game.Tests.Database [Test] public void TestRollbackOnFailure() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { int loggedExceptionCount = 0; @@ -531,16 +531,16 @@ namespace osu.Game.Tests.Database Interlocked.Increment(ref loggedExceptionCount); }; - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - realmFactory.Context.Write(() => imported.Hash += "-changed"); + realm.Realm.Write(() => imported.Hash += "-changed"); - checkBeatmapSetCount(realmFactory.Context, 1); - checkBeatmapCount(realmFactory.Context, 12); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkBeatmapSetCount(realm.Realm, 1); + checkBeatmapCount(realm.Realm, 12); + checkSingleReferencedFileCount(realm.Realm, 18); string? brokenTempFilename = TestResources.GetTestBeatmapForImport(); @@ -565,10 +565,10 @@ namespace osu.Game.Tests.Database { } - checkBeatmapSetCount(realmFactory.Context, 1); - checkBeatmapCount(realmFactory.Context, 12); + checkBeatmapSetCount(realm.Realm, 1); + checkBeatmapCount(realm.Realm, 12); - checkSingleReferencedFileCount(realmFactory.Context, 18); + checkSingleReferencedFileCount(realm.Realm, 18); Assert.AreEqual(1, loggedExceptionCount); @@ -579,18 +579,18 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenDeleteThenImportOptimisedPath() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); - var imported = await LoadOszIntoStore(importer, realmFactory.Context); + var imported = await LoadOszIntoStore(importer, realm.Realm); - deleteBeatmapSet(imported, realmFactory.Context); + deleteBeatmapSet(imported, realm.Realm); Assert.IsTrue(imported.DeletePending); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); @@ -603,18 +603,18 @@ namespace osu.Game.Tests.Database [Test] public void TestImportThenDeleteThenImportNonOptimisedPath() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new NonOptimisedBeatmapImporter(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); @@ -627,22 +627,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 +653,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWithDuplicateBeatmapIDs() { - 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 metadata = new BeatmapMetadata { @@ -667,7 +667,7 @@ namespace osu.Game.Tests.Database } }; - var ruleset = realmFactory.Context.All().First(); + var ruleset = realm.Realm.All().First(); var toImport = new BeatmapSetInfo { @@ -699,15 +699,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 +716,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 +740,7 @@ namespace osu.Game.Tests.Database await importer.Import(temp); - EnsureLoaded(realmFactory.Context); + EnsureLoaded(realm.Realm); } finally { @@ -752,10 +752,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 +780,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 +794,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 +830,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 +845,22 @@ namespace osu.Game.Tests.Database [Test] public void TestUpdateBeatmapInfo() { - RunTestWithRealmAsync(async (realmFactory, storage) => + RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapModelManager(realmFactory, storage); - using var store = new RulesetStore(realmFactory, storage); + using var importer = new BeatmapModelManager(realm, storage); + using var store = new RulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); await importer.Import(temp); // Update via the beatmap, not the beatmap info, to ensure correct linking - BeatmapSetInfo setToUpdate = realmFactory.Context.All().First(); + BeatmapSetInfo setToUpdate = realm.Realm.All().First(); var beatmapToUpdate = setToUpdate.Beatmaps.First(); - realmFactory.Context.Write(() => beatmapToUpdate.DifficultyName = "updated"); + realm.Realm.Write(() => beatmapToUpdate.DifficultyName = "updated"); - BeatmapInfo updatedInfo = realmFactory.Context.All().First(b => b.ID == beatmapToUpdate.ID); + BeatmapInfo updatedInfo = realm.Realm.All().First(b => b.ID == beatmapToUpdate.ID); Assert.That(updatedInfo.DifficultyName, Is.EqualTo("updated")); }); } @@ -1004,8 +1004,8 @@ namespace osu.Game.Tests.Database public class NonOptimisedBeatmapImporter : BeatmapImporter { - public NonOptimisedBeatmapImporter(RealmContextFactory realmFactory, Storage storage) - : base(realmFactory, storage) + public NonOptimisedBeatmapImporter(RealmAccess realm, Storage storage) + : base(realm, storage) { } diff --git a/osu.Game.Tests/Database/FileStoreTests.cs b/osu.Game.Tests/Database/FileStoreTests.cs index 3cb4705381..008aa3d833 100644 --- a/osu.Game.Tests/Database/FileStoreTests.cs +++ b/osu.Game.Tests/Database/FileStoreTests.cs @@ -19,10 +19,10 @@ namespace osu.Game.Tests.Database [Test] public void TestImportFile() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realmAccess, storage) => { - var realm = realmFactory.Context; - var files = new RealmFileStore(realmFactory, storage); + var realm = realmAccess.Context; + 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.Context; + 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.Context; + 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.Context; + var files = new RealmFileStore(realmAccess, storage); var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm)); diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 3c62153d9e..2533c832e6 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -21,15 +21,15 @@ namespace osu.Game.Tests.Database [Test] public void TestConstructRealm() { - RunTestWithRealm((realmFactory, _) => { realmFactory.Run(realm => realm.Refresh()); }); + RunTestWithRealm((realm, _) => { realm.Run(r => r.Refresh()); }); } [Test] public void TestBlockOperations() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { } }); @@ -42,22 +42,22 @@ namespace osu.Game.Tests.Database [Test] public void TestNestedContextCreationWithSubscription() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { bool callbackRan = false; - realmFactory.RegisterCustomSubscription(realm => + realm.RegisterCustomSubscription(r => { - var subscription = realm.All().QueryAsyncWithNotifications((sender, changes, error) => + var subscription = r.All().QueryAsyncWithNotifications((sender, changes, error) => { - realmFactory.Run(_ => + realm.Run(_ => { callbackRan = true; }); }); // Force the callback above to run. - realmFactory.Run(r => r.Refresh()); + realm.Run(rr => rr.Refresh()); subscription?.Dispose(); return null; @@ -70,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(); @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Database Assert.Throws(() => { - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { } }); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index d53fcb9ac7..2e3f708f79 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -21,11 +21,11 @@ namespace osu.Game.Tests.Database [Test] public void TestLiveEquality() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { - ILive beatmap = realmFactory.Run(realm => realm.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory)); + ILive beatmap = realm.Run(r => r.Write(_ => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realm)); - ILive beatmap2 = realmFactory.Run(realm => realm.All().First().ToLive(realmFactory)); + ILive beatmap2 = realm.Run(r => r.All().First().ToLive(realm)); Assert.AreEqual(beatmap, beatmap2); }); @@ -34,20 +34,20 @@ namespace osu.Game.Tests.Database [Test] public void TestAccessAfterStorageMigrate() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); ILive? liveBeatmap = null; - realmFactory.Run(realm => + realm.Run(r => { - realm.Write(r => r.Add(beatmap)); + r.Write(_ => r.Add(beatmap)); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { // recycle realm before migrating } @@ -66,13 +66,13 @@ namespace osu.Game.Tests.Database [Test] public void TestAccessAfterAttach() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - var liveBeatmap = beatmap.ToLive(realmFactory); + var liveBeatmap = beatmap.ToLive(realm); - realmFactory.Run(realm => realm.Write(r => r.Add(beatmap))); + realm.Run(r => r.Write(_ => r.Add(beatmap))); Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); }); @@ -98,16 +98,16 @@ namespace osu.Game.Tests.Database [Test] public void TestScopedReadWithoutContext() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -127,16 +127,16 @@ namespace osu.Game.Tests.Database [Test] public void TestScopedWriteWithoutContext() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -153,10 +153,10 @@ namespace osu.Game.Tests.Database [Test] public void TestValueAccessNonManaged() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - var liveBeatmap = beatmap.ToLive(realmFactory); + var liveBeatmap = beatmap.ToLive(realm); Assert.DoesNotThrow(() => { @@ -168,17 +168,17 @@ namespace osu.Game.Tests.Database [Test] public void TestValueAccessWithOpenContextFails() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Database }); // Can't be used, even from within a valid context. - realmFactory.Run(threadContext => + realm.Run(threadContext => { Assert.Throws(() => { @@ -207,16 +207,16 @@ namespace osu.Game.Tests.Database [Test] public void TestValueAccessWithoutOpenContextFails() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(threadContext => + realm.Run(threadContext => { var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); @@ -235,18 +235,18 @@ namespace osu.Game.Tests.Database [Test] public void TestLiveAssumptions() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { int changesTriggered = 0; - realmFactory.RegisterCustomSubscription(outerRealm => + realm.RegisterCustomSubscription(outerRealm => { outerRealm.All().QueryAsyncWithNotifications(gotChange); ILive? liveBeatmap = null; Task.Factory.StartNew(() => { - realmFactory.Run(innerRealm => + realm.Run(innerRealm => { var ruleset = CreateRuleset(); var beatmap = innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); @@ -255,7 +255,7 @@ namespace osu.Game.Tests.Database // not just a refresh from the resolved Live. innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata()))); - liveBeatmap = beatmap.ToLive(realmFactory); + liveBeatmap = beatmap.ToLive(realm); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely(); diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 1799b95905..d62ce3b585 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -24,11 +24,11 @@ namespace osu.Game.Tests.Database IEnumerable? resolvedItems = null; ChangeSet? lastChanges = null; - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); - var registration = realmFactory.RegisterForNotifications(realm => realm.All(), onChanged); + var registration = realm.RegisterForNotifications(r => r.All(), onChanged); testEventsArriving(true); @@ -37,10 +37,10 @@ namespace osu.Game.Tests.Database resolvedItems = null; lastChanges = null; - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) Assert.That(resolvedItems, Is.Empty); - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); testEventsArriving(true); @@ -50,34 +50,34 @@ namespace osu.Game.Tests.Database registration.Dispose(); - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); testEventsArriving(false); // And make sure even after another context loss we don't get firings. - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) Assert.That(resolvedItems, Is.Null); - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); testEventsArriving(false); void testEventsArriving(bool shouldArrive) { - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); if (shouldArrive) Assert.That(resolvedItems, Has.One.Items); else Assert.That(resolvedItems, Is.Null); - realmFactory.Write(realm => + realm.Write(r => { - realm.RemoveAll(); - realm.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); }); - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); if (shouldArrive) Assert.That(lastChanges?.DeletedIndices, Has.One.Items); @@ -98,39 +98,39 @@ namespace osu.Game.Tests.Database [Test] public void TestCustomRegisterWithContextLoss() { - RunTestWithRealm((realmFactory, _) => + RunTestWithRealm((realm, _) => { BeatmapSetInfo? beatmapSetInfo = null; - realmFactory.Write(realm => realm.Add(TestResources.CreateTestBeatmapSetInfo())); + realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); - var subscription = realmFactory.RegisterCustomSubscription(realm => + var subscription = realm.RegisterCustomSubscription(r => { - beatmapSetInfo = realm.All().First(); + beatmapSetInfo = r.All().First(); return new InvokeOnDisposal(() => beatmapSetInfo = null); }); Assert.That(beatmapSetInfo, Is.Not.Null); - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { // custom disposal action fired when context lost. Assert.That(beatmapSetInfo, Is.Null); } // re-registration after context restore. - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); Assert.That(beatmapSetInfo, Is.Not.Null); subscription.Dispose(); Assert.That(beatmapSetInfo, Is.Null); - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) Assert.That(beatmapSetInfo, Is.Null); - realmFactory.Run(realm => realm.Refresh()); + realm.Run(r => r.Refresh()); Assert.That(beatmapSetInfo, Is.Null); }); } diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index 0cee165f75..c2339dd9ad 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Database storage.DeleteDirectory(string.Empty); } - protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") + protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller)) { @@ -39,22 +39,22 @@ namespace osu.Game.Tests.Database // ReSharper disable once AccessToDisposedClosure var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller)); - using (var realmFactory = new RealmContextFactory(testStorage, "client")) + using (var realm = new RealmAccess(testStorage, "client")) { - Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); - testAction(realmFactory, testStorage); + Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}"); + testAction(realm, testStorage); - realmFactory.Dispose(); + realm.Dispose(); - Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}"); - realmFactory.Compact(); - Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}"); + Logger.Log($"Final database size: {getFileSize(testStorage, realm)}"); + realm.Compact(); + Logger.Log($"Final database size after compact: {getFileSize(testStorage, realm)}"); } })); } } - protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "") + protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "") { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller)) { @@ -62,15 +62,15 @@ namespace osu.Game.Tests.Database { var testStorage = storage.GetStorageForDirectory(caller); - using (var realmFactory = new RealmContextFactory(testStorage, "client")) + using (var realm = new RealmAccess(testStorage, "client")) { - Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); - await testAction(realmFactory, testStorage); + Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}"); + await testAction(realm, testStorage); - realmFactory.Dispose(); + realm.Dispose(); - Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}"); - realmFactory.Compact(); + Logger.Log($"Final database size: {getFileSize(testStorage, realm)}"); + realm.Compact(); } })); } @@ -138,11 +138,11 @@ namespace osu.Game.Tests.Database } } - private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory) + private static long getFileSize(Storage testStorage, RealmAccess realm) { try { - using (var stream = testStorage.GetStream(realmFactory.Filename)) + using (var stream = testStorage.GetStream(realm.Filename)) return stream?.Length ?? 0; } catch diff --git a/osu.Game.Tests/Database/RulesetStoreTests.cs b/osu.Game.Tests/Database/RulesetStoreTests.cs index 4416da6f92..7544142b70 100644 --- a/osu.Game.Tests/Database/RulesetStoreTests.cs +++ b/osu.Game.Tests/Database/RulesetStoreTests.cs @@ -12,37 +12,37 @@ namespace osu.Game.Tests.Database [Test] public void TestCreateStore() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realm, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); - Assert.AreEqual(4, realmFactory.Context.All().Count()); + Assert.AreEqual(4, realm.Realm.All().Count()); }); } [Test] public void TestCreateStoreTwiceDoesntAddRulesetsAgain() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realmFactory, storage); - var rulesets2 = new RulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realm, storage); + var rulesets2 = new RulesetStore(realm, storage); Assert.AreEqual(4, rulesets.AvailableRulesets.Count()); Assert.AreEqual(4, rulesets2.AvailableRulesets.Count()); Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First()); - Assert.AreEqual(4, realmFactory.Context.All().Count()); + Assert.AreEqual(4, realm.Realm.All().Count()); }); } [Test] public void TestRetrievedRulesetsAreDetached() { - RunTestWithRealm((realmFactory, storage) => + RunTestWithRealm((realm, storage) => { - var rulesets = new RulesetStore(realmFactory, storage); + var rulesets = new RulesetStore(realm, storage); Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged); Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged); diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index c1041e9fd6..4b8816f142 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Database private RealmKeyBindingStore keyBindingStore; - private RealmContextFactory realmContextFactory; + private RealmAccess realm; [SetUp] public void SetUp() @@ -33,8 +33,8 @@ namespace osu.Game.Tests.Database storage = new NativeStorage(directory.FullName); - realmContextFactory = new RealmContextFactory(storage, "test"); - keyBindingStore = new RealmKeyBindingStore(realmContextFactory, new ReadableKeyCombinationProvider()); + realm = new RealmAccess(storage, "test"); + keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider()); } [Test] @@ -60,7 +60,7 @@ 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(realm => { realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A))); realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S))); @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Database private int queryCount(GlobalAction? match = null) { - return realmContextFactory.Run(realm => + return realm.Run(realm => { var results = realm.All(); if (match.HasValue) @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Database keyBindingStore.Register(testContainer, Enumerable.Empty()); - realmContextFactory.Run(outerRealm => + realm.Run(outerRealm => { var backBinding = outerRealm.All().Single(k => k.ActionInt == (int)GlobalAction.Back); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Database var tsr = ThreadSafeReference.Create(backBinding); - realmContextFactory.Run(innerRealm => + realm.Run(innerRealm => { var binding = innerRealm.ResolveReference(tsr); innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace)); @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Database [TearDown] public void TearDown() { - realmContextFactory.Dispose(); + realm.Dispose(); storage.DeleteDirectory(string.Empty); } diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index f0ebd7a8cc..88862ea28b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -261,7 +261,7 @@ namespace osu.Game.Tests.Gameplay public AudioManager AudioManager => Audio; public IResourceStore Files => null; public new IResourceStore Resources => base.Resources; - public RealmContextFactory RealmContextFactory => null; + public RealmAccess RealmAccess => null; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null; #endregion diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 1d639c6418..e35e4d9b15 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -45,8 +45,8 @@ namespace osu.Game.Tests.Online [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host)); } @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Online testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; - ContextFactory.Write(r => r.RemoveAll()); - ContextFactory.Write(r => r.RemoveAll()); + Access.Write(r => r.RemoveAll()); + Access.Write(r => r.RemoveAll()); selectedItem.Value = new PlaylistItem { @@ -166,22 +166,22 @@ namespace osu.Game.Tests.Online public Task> CurrentImportTask { get; private set; } - public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) - : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) + public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) + : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) { } - protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) + protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) { - return new TestBeatmapModelManager(this, storage, contextFactory, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(this, storage, realm, rulesets, onlineLookupQueue); } internal class TestBeatmapModelManager : BeatmapModelManager { private readonly TestBeatmapManager testBeatmapManager; - public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) - : base(databaseContextFactory, storage, beatmapOnlineLookupQueue) + public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + : base(databaseAccess, storage, beatmapOnlineLookupQueue) { this.testBeatmapManager = testBeatmapManager; } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 4ab4c08353..fbfa7eda6a 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -47,10 +47,10 @@ namespace osu.Game.Tests.Visual.Background [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(Access); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 18572ac211..fd0645a1e9 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index d4282ff21e..07b2bdcba3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 99c867b014..0bf1d60ac5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -43,9 +43,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(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 373b165acc..063d886729 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -61,9 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 15ebe0ee00..8c79c468d7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps = new List(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 012a2fd960..77e2c9c714 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index d547b42891..4270818b1a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -33,9 +33,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 965b142ed7..56e64292c6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 1c346e09d5..afb60b62aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 221732910b..79b29f0eca 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 0b0006e437..6ed57e9899 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -34,9 +34,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(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 39cde0ad87..bd95b297d4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index 0f314242b4..7cb29895eb 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Navigation .ChildrenOfType().SingleOrDefault(); private RealmKeyBinding firstOsuRulesetKeyBindings => Game.Dependencies - .Get().Context + .Get().Context .All() .AsEnumerable() .First(k => k.RulesetName == "osu" && k.ActionInt == 0); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index bc9f759bdd..716e3a535d 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -40,9 +40,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(Access)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); } [SetUpSteps] diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index a77480ee54..13b4af5223 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -36,13 +36,13 @@ 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(realm => { var beatmapInfo = realm.All() .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2e1a66be5f..2f5594379b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -42,10 +42,10 @@ namespace osu.Game.Tests.Visual.SongSelect { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - Dependencies.Cache(ContextFactory); + dependencies.Cache(rulesetStore = new RulesetStore(Access)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Access, Scheduler)); + Dependencies.Cache(Access); return dependencies; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index ca8e9d2eff..4868a4a075 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Access); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6295a52bdd..d8a39dda01 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -47,9 +47,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(Access)); + Dependencies.Cache(Access); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); Dependencies.Cache(music = new MusicController()); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 3aa5a759e6..a0657ffdf6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -28,10 +28,10 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - Dependencies.Cache(ContextFactory); + Dependencies.Cache(rulesets = new RulesetStore(Access)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Access, Scheduler)); + Dependencies.Cache(Access); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index f43354514b..2278d3f8bf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.UserInterface private BeatmapInfo beatmapInfo; [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } [Cached] private readonly DialogOverlay dialogOverlay; @@ -87,10 +87,10 @@ namespace osu.Game.Tests.Visual.UserInterface { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); - Dependencies.Cache(ContextFactory); + dependencies.Cache(rulesetStore = new RulesetStore(Access)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Access, Scheduler)); + Dependencies.Cache(Access); var imported = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void Setup() => Schedule(() => { - realmFactory.Run(realm => + realm.Run(realm => { // Due to soft deletions, we can re-use deleted scores between test runs scoreManager.Undelete(realm.All().Where(s => s.DeletePending).ToList()); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 43e4b482bd..ddc1d054cc 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -41,11 +41,11 @@ namespace osu.Game.Beatmaps private readonly WorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue? onlineBeatmapLookupQueue; - private readonly RealmContextFactory contextFactory; + private readonly RealmAccess realm; - public BeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false) + public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false) { - this.contextFactory = contextFactory; + this.realm = realm; if (performOnlineLookups) { @@ -55,11 +55,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 +70,8 @@ namespace osu.Game.Beatmaps return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); } - protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) => - new BeatmapModelManager(contextFactory, storage, onlineLookupQueue); + protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) => + new BeatmapModelManager(realm, storage, onlineLookupQueue); /// /// Create a new . @@ -119,7 +119,7 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to hide. public void Hide(BeatmapInfo beatmapInfo) { - contextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -138,7 +138,7 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to restore. public void Restore(BeatmapInfo beatmapInfo) { - contextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -153,7 +153,7 @@ namespace osu.Game.Beatmaps public void RestoreAll() { - contextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -171,7 +171,7 @@ namespace osu.Game.Beatmaps /// A list of available . public List GetAllUsableBeatmapSets() { - return contextFactory.Run(realm => + return realm.Run(realm => { realm.Refresh(); return realm.All().Where(b => !b.DeletePending).Detach(); @@ -185,7 +185,7 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public ILive? QueryBeatmapSet(Expression> query) { - return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.ToLive(contextFactory)); + return realm.Run(r => r.All().FirstOrDefault(query)?.ToLive(realm)); } #region Delegation to BeatmapModelManager (methods which previously existed locally). @@ -240,7 +240,7 @@ namespace osu.Game.Beatmaps public void Delete(Expression>? filter = null, bool silent = false) { - contextFactory.Run(realm => + realm.Run(realm => { var items = realm.All().Where(s => !s.DeletePending && !s.Protected); @@ -253,7 +253,7 @@ namespace osu.Game.Beatmaps public void UndeleteAll() { - contextFactory.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList())); + realm.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList())); } public void Undelete(List items, bool silent = false) @@ -312,7 +312,7 @@ 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(realm => { var refetch = realm.Find(importedBeatmap.ID)?.Detach(); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index ead86c1059..167d77d6f6 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -33,8 +33,8 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - public BeatmapModelManager(RealmContextFactory contextFactory, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) - : base(contextFactory, storage, onlineLookupQueue) + public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) + : base(realm, storage, onlineLookupQueue) { } @@ -98,12 +98,12 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo? QueryBeatmap(Expression> query) { - return ContextFactory.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return Access.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } public void Update(BeatmapSetInfo item) { - ContextFactory.Write(realm => + Access.Write(realm => { var existing = realm.Find(item.ID); item.CopyChangesToRealm(existing); diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 6947752c47..d3f356bb24 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -100,7 +100,7 @@ namespace osu.Game.Beatmaps TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore; ITrackStore IBeatmapResourceProvider.Tracks => trackStore; AudioManager IStorageResourceProvider.AudioManager => audioManager; - RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; + RealmAccess IStorageResourceProvider.RealmAccess => null; IResourceStore IStorageResourceProvider.Files => files; IResourceStore IStorageResourceProvider.Resources => resources; IResourceStore IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore); diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs index 2bba20fb09..e5d2d572c8 100644 --- a/osu.Game/Configuration/SettingsStore.cs +++ b/osu.Game/Configuration/SettingsStore.cs @@ -10,11 +10,11 @@ namespace osu.Game.Configuration // this class mostly exists as a wrapper to avoid breaking the ruleset API (see usage in RulesetConfigManager). // it may cease to exist going forward, depending on how the structure of the config data layer changes. - public readonly RealmContextFactory Realm; + public readonly RealmAccess Realm; - public SettingsStore(RealmContextFactory realmFactory) + public SettingsStore(RealmAccess realm) { - Realm = realmFactory; + Realm = realm; } } } diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index edce99e302..a0787e81e6 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -35,7 +35,7 @@ namespace osu.Game.Database 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!; @@ -101,7 +101,7 @@ namespace osu.Game.Database { using (var ef = efContextFactory.Get()) { - realmContextFactory.Write(realm => + realm.Write(realm => { // 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. @@ -158,7 +158,7 @@ namespace osu.Game.Database int count = existingBeatmapSets.Count(); - realmContextFactory.Run(realm => + realm.Run(realm => { log($"Found {count} beatmaps in EF"); @@ -280,7 +280,7 @@ namespace osu.Game.Database int count = existingScores.Count(); - realmContextFactory.Run(realm => + realm.Run(realm => { log($"Found {count} scores in EF"); @@ -369,7 +369,7 @@ namespace osu.Game.Database break; } - realmContextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { @@ -428,7 +428,7 @@ namespace osu.Game.Database log("Beginning settings migration to realm"); - realmContextFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmAccess.cs similarity index 98% rename from osu.Game/Database/RealmContextFactory.cs rename to osu.Game/Database/RealmAccess.cs index 778092c543..2f397639c9 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -32,7 +32,7 @@ namespace osu.Game.Database /// /// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage. /// - public class RealmContextFactory : IDisposable + public class RealmAccess : IDisposable { private readonly Storage storage; @@ -123,7 +123,7 @@ namespace osu.Game.Database /// The game storage which will be used to create the realm backing file. /// The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified. /// An EF factory used only for migration purposes. - public RealmContextFactory(Storage storage, string filename, IDatabaseContextFactory? efContextFactory = null) + public RealmAccess(Storage storage, string filename, IDatabaseContextFactory? efContextFactory = null) { this.storage = storage; this.efContextFactory = efContextFactory; @@ -365,7 +365,7 @@ namespace osu.Game.Database private Realm createContext() { if (isDisposed) - throw new ObjectDisposedException(nameof(RealmContextFactory)); + throw new ObjectDisposedException(nameof(RealmAccess)); bool tookSemaphoreLock = false; @@ -592,7 +592,7 @@ namespace osu.Game.Database public IDisposable BlockAllOperations() { if (isDisposed) - throw new ObjectDisposedException(nameof(RealmContextFactory)); + throw new ObjectDisposedException(nameof(RealmAccess)); SynchronizationContext? syncContext = null; @@ -652,7 +652,7 @@ namespace osu.Game.Database throw; } - return new InvokeOnDisposal(this, factory => + return new InvokeOnDisposal(this, factory => { factory.contextCreationLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index df5e165f8e..29159fd5be 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -24,17 +24,17 @@ namespace osu.Game.Database /// private readonly T data; - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; /// /// Construct a new instance of live realm data. /// /// The realm data. - /// The realm factory the data was sourced from. May be null for an unmanaged object. - public RealmLive(T data, RealmContextFactory realmFactory) + /// The realm factory the data was sourced from. May be null for an unmanaged object. + public RealmLive(T data, RealmAccess realm) { this.data = data; - this.realmFactory = realmFactory; + this.realm = realm; ID = data.ID; } @@ -51,7 +51,7 @@ namespace osu.Game.Database return; } - realmFactory.Run(realm => + realm.Run(realm => { perform(retrieveFromID(realm, ID)); }); @@ -66,7 +66,7 @@ namespace osu.Game.Database if (!IsManaged) return perform(data); - return realmFactory.Run(realm => + return realm.Run(realm => { var returnData = perform(retrieveFromID(realm, ID)); @@ -104,7 +104,7 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads"); - return realmFactory.Context.Find(ID); + return realm.Realm.Find(ID); } } diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index d9026d165d..d4f8978ac5 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -216,16 +216,16 @@ namespace osu.Game.Database return new RealmLiveUnmanaged(realmObject); } - public static List> ToLive(this IEnumerable realmList, RealmContextFactory realmContextFactory) + public static List> ToLive(this IEnumerable realmList, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { - return realmList.Select(l => new RealmLive(l, realmContextFactory)).Cast>().ToList(); + return realmList.Select(l => new RealmLive(l, realm)).Cast>().ToList(); } - public static ILive ToLive(this T realmObject, RealmContextFactory realmContextFactory) + public static ILive ToLive(this T realmObject, RealmAccess realm) where T : RealmObject, IHasGuidPrimaryKey { - return new RealmLive(realmObject, realmContextFactory); + return new RealmLive(realmObject, realm); } /// @@ -271,8 +271,8 @@ namespace osu.Game.Database public static IDisposable? QueryAsyncWithNotifications(this IRealmCollection collection, NotificationCallbackDelegate callback) where T : RealmObjectBase { - if (!RealmContextFactory.CurrentThreadSubscriptionsAllowed) - throw new InvalidOperationException($"Make sure to call {nameof(RealmContextFactory)}.{nameof(RealmContextFactory.RegisterForNotifications)}"); + if (!RealmAccess.CurrentThreadSubscriptionsAllowed) + throw new InvalidOperationException($"Make sure to call {nameof(RealmAccess)}.{nameof(RealmAccess.RegisterForNotifications)}"); return collection.SubscribeForNotifications(callback); } diff --git a/osu.Game/IO/IStorageResourceProvider.cs b/osu.Game/IO/IStorageResourceProvider.cs index 950b5aae09..b381ac70b0 100644 --- a/osu.Game/IO/IStorageResourceProvider.cs +++ b/osu.Game/IO/IStorageResourceProvider.cs @@ -28,7 +28,7 @@ namespace osu.Game.IO /// /// Access realm. /// - RealmContextFactory RealmContextFactory { get; } + RealmAccess RealmAccess { get; } /// /// Create a texture loader store based on an underlying data store. diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 9cd9865441..d54c049c99 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Input.Bindings private IDisposable realmSubscription; [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } public override IEnumerable DefaultKeyBindings => ruleset.CreateInstance().GetDefaultKeyBindings(variant ?? 0); @@ -49,13 +49,13 @@ namespace osu.Game.Input.Bindings private IQueryable queryRealmKeyBindings() { string rulesetName = ruleset?.ShortName; - return realmFactory.Context.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); + return realm.Realm.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); } protected override void LoadComplete() { - realmSubscription = realmFactory.RegisterForNotifications(realm => queryRealmKeyBindings(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(realm => 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. diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 60f7eb2198..cccd42a9aa 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -16,12 +16,12 @@ namespace osu.Game.Input { public class RealmKeyBindingStore { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; private readonly ReadableKeyCombinationProvider keyCombinationProvider; - public RealmKeyBindingStore(RealmContextFactory realmFactory, ReadableKeyCombinationProvider keyCombinationProvider) + public RealmKeyBindingStore(RealmAccess realm, ReadableKeyCombinationProvider keyCombinationProvider) { - this.realmFactory = realmFactory; + this.realm = realm; this.keyCombinationProvider = keyCombinationProvider; } @@ -34,7 +34,7 @@ namespace osu.Game.Input { List combinations = new List(); - realmFactory.Run(context => + realm.Run(context => { foreach (var action in context.All().Where(b => string.IsNullOrEmpty(b.RulesetName) && (GlobalAction)b.ActionInt == globalAction)) { @@ -56,7 +56,7 @@ namespace osu.Game.Input /// The rulesets to populate defaults from. public void Register(KeyBindingContainer container, IEnumerable rulesets) { - realmFactory.Run(realm => + realm.Run(realm => { using (var transaction = realm.BeginWrite()) { diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 3f45afb6b2..f54dc30620 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -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.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 28c41bec33..c562695ac1 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -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!; /// /// 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 = realmContextFactory.RegisterForNotifications(realm => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(realm => 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() - .Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum); + return realm.Realm + .All() + .Filter("OnlineID == $0 && MD5Hash == $1 && BeatmapSet.DeletePending == false", onlineId, checksum); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 64ceee9fe6..81dfc811a4 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -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.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 710a7be8d4..7a6a126900 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -149,7 +149,7 @@ namespace osu.Game private MultiplayerClient multiplayerClient; - private RealmContextFactory realmFactory; + private RealmAccess realm; protected override Container 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(RulesetStore); // Backup is taken here rather than in EFToRealmMigrator to avoid recycling realm contexts @@ -205,7 +205,7 @@ 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)) @@ -225,7 +225,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(SkinManager); EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); @@ -240,8 +240,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 +259,7 @@ namespace osu.Game dependencies.Cache(scorePerformanceManager); AddInternal(scorePerformanceManager); - dependencies.CacheAs(rulesetConfigCache = new RulesetConfigCache(realmFactory, RulesetStore)); + dependencies.CacheAs(rulesetConfigCache = new RulesetConfigCache(realm, RulesetStore)); var powerStatus = CreateBatteryInfo(); if (powerStatus != null) @@ -303,7 +303,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 +405,7 @@ namespace osu.Game Scheduler.Add(() => { - realmBlocker = realmFactory.BlockAllOperations(); + realmBlocker = realm.BlockAllOperations(); readyToRun.Set(); }, false); @@ -483,7 +483,7 @@ namespace osu.Game BeatmapManager?.Dispose(); LocalConfig?.Dispose(); - realmFactory?.Dispose(); + realm?.Dispose(); } } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8df7ed9736..8450446473 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -69,7 +69,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() @@ -81,9 +81,9 @@ namespace osu.Game.Overlays } private IQueryable queryRealmBeatmapSets() => - realmFactory.Context - .All() - .Where(s => !s.DeletePending); + realm.Realm + .All() + .Where(s => !s.DeletePending); protected override void LoadComplete() { @@ -94,7 +94,7 @@ namespace osu.Game.Overlays foreach (var s in queryRealmBeatmapSets()) beatmapSets.Add(s.Detach()); - beatmapSubscription = realmFactory.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index c5854981e6..3b94cae171 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -19,7 +19,7 @@ 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; @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings Action = () => { // Blocking operations implicitly causes a Compact(). - using (realmFactory.BlockAllOperations()) + using (realm.BlockAllOperations()) { } } @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { try { - var token = realmFactory.BlockAllOperations(); + var token = realm.BlockAllOperations(); blockAction.Enabled.Value = false; diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 60aff91301..91883e4f41 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -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,7 +386,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateStoreFromButton(KeyButton button) { - realmFactory.Run(realm => + realm.Run(realm => { var binding = realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); realm.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 5b8a52240e..922d371261 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -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() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant) - .Detach()); + var bindings = realm.Run(r => r.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant) + .Detach()); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 6e5cd0da2d..af3fd5c9bf 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -47,15 +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 queryRealmSkins() => - realmFactory.Context.All() - .Where(s => !s.DeletePending) - .OrderByDescending(s => s.Protected) // protected skins should be at the top. - .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase); + realm.Realm.All() + .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) @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realmFactory.RegisterForNotifications(realm => queryRealmSkins(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(realm => 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. @@ -130,7 +130,7 @@ namespace osu.Game.Overlays.Settings.Sections { int protectedCount = queryRealmSkins().Count(s => s.Protected); - skinItems = queryRealmSkins().ToLive(realmFactory); + skinItems = queryRealmSkins().ToLive(realm); skinItems.Insert(protectedCount, random_skin_info); diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 75bebfa763..c855b76680 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -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().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value); + var realmKeyBinding = realm.Realm.All().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value); if (realmKeyBinding != null) { diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 60a6b70221..cfa20e0b87 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Configuration public abstract class RulesetConfigManager : ConfigManager, 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().Where(b => b.RulesetName == rulesetName && b.Variant == variant).ToList(); + databasedSettings = realm.Realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).ToList(); } } @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Configuration pendingWrites.Clear(); } - realmFactory?.Write(realm => + realm?.Write(realm => { foreach (var c in changed) { @@ -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); } diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index dee13e74a5..c4f1933cd8 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -13,14 +13,14 @@ namespace osu.Game.Rulesets { public class RulesetConfigCache : Component, IRulesetConfigCache { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; private readonly RulesetStore rulesets; private readonly Dictionary configCache = new Dictionary(); - public RulesetConfigCache(RealmContextFactory realmFactory, RulesetStore rulesets) + public RulesetConfigCache(RealmAccess realm, RulesetStore rulesets) { - this.realmFactory = realmFactory; + this.realm = realm; this.rulesets = rulesets; } @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets { base.LoadComplete(); - var settingsStore = new SettingsStore(realmFactory); + var settingsStore = new SettingsStore(realm); // let's keep things simple for now and just retrieve all the required configs at startup.. foreach (var ruleset in rulesets.AvailableRulesets) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index a9e5ff797c..606bc65599 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets { public class RulesetStore : IDisposable, IRulesetStore { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; private const string ruleset_library_prefix = @"osu.Game.Rulesets"; @@ -31,9 +31,9 @@ namespace osu.Game.Rulesets private readonly List availableRulesets = new List(); - public RulesetStore(RealmContextFactory realmFactory, Storage? storage = null) + public RulesetStore(RealmAccess realm, Storage? storage = null) { - this.realmFactory = realmFactory; + this.realm = realm; // On android in release configuration assemblies are loaded from the apk directly into memory. // We cannot read assemblies from cwd, so should check loaded assemblies instead. @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets private void addMissingRulesets() { - realmFactory.Write(realm => + realm.Write(realm => { var rulesets = realm.All(); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index f895134f97..e712d170cd 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -25,21 +25,21 @@ namespace osu.Game.Scoring { public class ScoreManager : IModelManager, IModelImporter { - private readonly RealmContextFactory contextFactory; + private readonly RealmAccess realm; private readonly Scheduler scheduler; private readonly Func difficulties; private readonly OsuConfigManager configManager; private readonly ScoreModelManager scoreModelManager; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory, Scheduler scheduler, + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) { - this.contextFactory = contextFactory; + this.realm = realm; this.scheduler = scheduler; this.difficulties = difficulties; this.configManager = configManager; - scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, contextFactory); + scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, realm); } public Score GetScore(ScoreInfo score) => scoreModelManager.GetScore(score); @@ -51,7 +51,7 @@ namespace osu.Game.Scoring /// The first result for the provided query, or null if no results were found. public ScoreInfo Query(Expression> query) { - return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return realm.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } /// @@ -254,7 +254,7 @@ namespace osu.Game.Scoring public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - contextFactory.Run(realm => + realm.Run(realm => { var items = realm.All() .Where(s => !s.DeletePending); diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 5e560effa1..2147ff1ba1 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -29,8 +29,8 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; - public ScoreModelManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmContextFactory contextFactory) - : base(storage, contextFactory) + public ScoreModelManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm) + : base(storage, realm) { this.rulesets = rulesets; this.beatmaps = beatmaps; @@ -74,7 +74,7 @@ namespace osu.Game.Scoring public override bool IsAvailableLocally(ScoreInfo model) { - return ContextFactory.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); + return Access.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); } } } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index e66ecc74e1..fceb083916 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Menu private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, Framework.Game game, RealmContextFactory realmContextFactory) + private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm) { // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); @@ -97,9 +97,9 @@ namespace osu.Game.Screens.Menu // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) { - realmContextFactory.Run(realm => + realm.Run(r => { - var usableBeatmapSets = realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); + var usableBeatmapSets = r.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); int setCount = usableBeatmapSets.Count; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5a6295fe74..8e0fdea0f8 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -179,24 +179,24 @@ namespace osu.Game.Screens.Select if (!loadedTestBeatmaps) { - realmFactory.Run(realm => loadBeatmapSets(getBeatmapSets(realm))); + realm.Run(realm => loadBeatmapSets(getBeatmapSets(realm))); } } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } protected override void LoadComplete() { base.LoadComplete(); - subscriptionSets = realmFactory.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); + subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); + subscriptionBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realmFactory.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realmFactory.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); + subscriptionDeletedSets = realm.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Select foreach (var id in realmSets) { if (!root.BeatmapSetsByID.ContainsKey(id)) - UpdateBeatmapSet(realmFactory.Context.Find(id).Detach()); + UpdateBeatmapSet(realm.Realm.Find(id).Detach()); } foreach (var id in root.BeatmapSetsByID.Keys) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index c03d464ef6..021dfd06f7 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Select.Carousel private IBindable ruleset { get; set; } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } [Resolved] private IAPIProvider api { get; set; } @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realmFactory.RegisterForNotifications(realm => + scoreSubscription = realm.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 5f288b972b..3d262f8b97 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Leaderboards private RulesetStore rulesets { get; set; } [Resolved] - private RealmContextFactory realmFactory { get; set; } + private RealmAccess realm { get; set; } private BeatmapInfo beatmapInfo; @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realmFactory.RegisterForNotifications(realm => + scoreSubscription = realm.RegisterForNotifications(realm => realm.All() .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), (_, changes, ___) => @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - realmFactory.Run(realm => + realm.Run(realm => { var scores = realm.All() .AsEnumerable() diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 4abde56ea6..3cf9f79611 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Spectate } [Resolved] - private RealmContextFactory realmContextFactory { get; set; } + private RealmAccess realm { get; set; } private IDisposable realmSubscription; @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Spectate playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); - realmSubscription = realmContextFactory.RegisterForNotifications( + realmSubscription = realm.RegisterForNotifications( realm => realm.All().Where(s => !s.DeletePending), beatmapsChanged); foreach ((int id, var _) in userMap) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index d606d94b97..3685a26e26 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -43,8 +43,8 @@ namespace osu.Game.Skinning protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null) { - SkinInfo = resources?.RealmContextFactory != null - ? skin.ToLive(resources.RealmContextFactory) + SkinInfo = resources?.RealmAccess != null + ? skin.ToLive(resources.RealmAccess) // This path should only be used in some tests. : skin.ToLiveUnmanaged(); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 82bcd3b292..66956325da 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -54,7 +54,7 @@ namespace osu.Game.Skinning }; private readonly SkinModelManager skinModelManager; - private readonly RealmContextFactory contextFactory; + private readonly RealmAccess realm; private readonly IResourceStore userFiles; @@ -68,9 +68,9 @@ namespace osu.Game.Skinning /// public Skin DefaultLegacySkin { get; } - public SkinManager(Storage storage, RealmContextFactory contextFactory, GameHost host, IResourceStore resources, AudioManager audio, Scheduler scheduler) + public SkinManager(Storage storage, RealmAccess realm, GameHost host, IResourceStore resources, AudioManager audio, Scheduler scheduler) { - this.contextFactory = contextFactory; + this.realm = realm; this.audio = audio; this.scheduler = scheduler; this.host = host; @@ -78,7 +78,7 @@ namespace osu.Game.Skinning userFiles = new StorageBackedResourceStore(storage.GetStorageForDirectory("files")); - skinModelManager = new SkinModelManager(storage, contextFactory, host, this); + skinModelManager = new SkinModelManager(storage, realm, host, this); var defaultSkins = new[] { @@ -87,7 +87,7 @@ namespace osu.Game.Skinning }; // Ensure the default entries are present. - contextFactory.Write(realm => + realm.Write(realm => { foreach (var skin in defaultSkins) { @@ -110,10 +110,10 @@ namespace osu.Game.Skinning public void SelectRandomSkin() { - contextFactory.Run(realm => + realm.Run(r => { // choose from only user skins, removing the current selection to ensure a new one is chosen. - var randomChoices = realm.All().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); + var randomChoices = r.All().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray(); if (randomChoices.Length == 0) { @@ -123,7 +123,7 @@ namespace osu.Game.Skinning var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length)); - CurrentSkinInfo.Value = chosen.ToLive(contextFactory); + CurrentSkinInfo.Value = chosen.ToLive(realm); }); } @@ -179,7 +179,7 @@ namespace osu.Game.Skinning /// The first result for the provided query, or null if no results were found. public ILive Query(Expression> query) { - return contextFactory.Run(realm => realm.All().FirstOrDefault(query)?.ToLive(contextFactory)); + return realm.Run(r => r.All().FirstOrDefault(query)?.ToLive(realm)); } public event Action SourceChanged; @@ -234,7 +234,7 @@ namespace osu.Game.Skinning AudioManager IStorageResourceProvider.AudioManager => audio; IResourceStore IStorageResourceProvider.Resources => resources; IResourceStore IStorageResourceProvider.Files => userFiles; - RealmContextFactory IStorageResourceProvider.RealmContextFactory => contextFactory; + RealmAccess IStorageResourceProvider.RealmAccess => realm; IResourceStore IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); #endregion @@ -289,7 +289,7 @@ namespace osu.Game.Skinning public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - contextFactory.Run(realm => + realm.Run(realm => { var items = realm.All() .Where(s => !s.Protected && !s.DeletePending); diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index a1926913a9..c93cdb17dd 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -27,8 +27,8 @@ namespace osu.Game.Skinning private readonly IStorageResourceProvider skinResources; - public SkinModelManager(Storage storage, RealmContextFactory contextFactory, GameHost host, IStorageResourceProvider skinResources) - : base(storage, contextFactory) + public SkinModelManager(Storage storage, RealmAccess realm, GameHost host, IStorageResourceProvider skinResources) + : base(storage, realm) { this.skinResources = skinResources; @@ -205,7 +205,7 @@ namespace osu.Game.Skinning private void populateMissingHashes() { - ContextFactory.Run(realm => + Access.Run(realm => { var skinsWithoutHashes = realm.All().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray(); diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 61178014ef..3d241e795c 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -44,8 +44,8 @@ namespace osu.Game.Stores private readonly BeatmapOnlineLookupQueue? onlineLookupQueue; - protected BeatmapImporter(RealmContextFactory contextFactory, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) - : base(storage, contextFactory) + protected BeatmapImporter(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null) + : base(storage, realm) { this.onlineLookupQueue = onlineLookupQueue; } @@ -165,7 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - return ContextFactory.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); + return Access.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 3d8e9f2703..23a860791e 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Stores protected readonly RealmFileStore Files; - protected readonly RealmContextFactory ContextFactory; + protected readonly RealmAccess Access; /// /// Fired when the user requests to view the resulting import. @@ -71,11 +71,11 @@ namespace osu.Game.Stores /// public Action? PostNotification { protected get; set; } - protected RealmArchiveModelImporter(Storage storage, RealmContextFactory contextFactory) + protected RealmArchiveModelImporter(Storage storage, RealmAccess realm) { - ContextFactory = contextFactory; + Access = realm; - Files = new RealmFileStore(contextFactory, storage); + Files = new RealmFileStore(realm, storage); } /// @@ -320,7 +320,7 @@ namespace osu.Game.Stores /// An optional cancellation token. public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - return ContextFactory.Run(realm => + return Access.Run(realm => { cancellationToken.ThrowIfCancellationRequested(); @@ -352,7 +352,7 @@ namespace osu.Game.Stores transaction.Commit(); } - return Task.FromResult((ILive?)existing.ToLive(ContextFactory)); + return Task.FromResult((ILive?)existing.ToLive(Access)); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -387,7 +387,7 @@ namespace osu.Game.Stores existing.DeletePending = false; transaction.Commit(); - return Task.FromResult((ILive?)existing.ToLive(ContextFactory)); + return Task.FromResult((ILive?)existing.ToLive(Access)); } LogForModel(item, @"Found existing but failed re-use check."); @@ -413,7 +413,7 @@ namespace osu.Game.Stores throw; } - return Task.FromResult((ILive?)item.ToLive(ContextFactory)); + return Task.FromResult((ILive?)item.ToLive(Access)); }); } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 115fbf721d..01eb90d6e8 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -24,10 +24,10 @@ namespace osu.Game.Stores { private readonly RealmFileStore realmFileStore; - protected RealmArchiveModelManager(Storage storage, RealmContextFactory contextFactory) - : base(storage, contextFactory) + protected RealmArchiveModelManager(Storage storage, RealmAccess realm) + : base(storage, realm) { - realmFileStore = new RealmFileStore(contextFactory, storage); + realmFileStore = new RealmFileStore(realm, storage); } public void DeleteFile(TModel item, RealmNamedFileUsage file) => @@ -45,7 +45,7 @@ namespace osu.Game.Stores // This method should be removed as soon as all the surrounding pieces support non-detached operations. if (!item.IsManaged) { - var managed = ContextFactory.Context.Find(item.ID); + var managed = Access.Context.Find(item.ID); managed.Realm.Write(() => operation(managed)); item.Files.Clear(); @@ -165,7 +165,7 @@ namespace osu.Game.Stores public bool Delete(TModel item) { - return ContextFactory.Run(realm => + return Access.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -180,7 +180,7 @@ namespace osu.Game.Stores public void Undelete(TModel item) { - ContextFactory.Run(realm => + Access.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); diff --git a/osu.Game/Stores/RealmFileStore.cs b/osu.Game/Stores/RealmFileStore.cs index ca371e29be..5edc1be954 100644 --- a/osu.Game/Stores/RealmFileStore.cs +++ b/osu.Game/Stores/RealmFileStore.cs @@ -24,15 +24,15 @@ namespace osu.Game.Stores [ExcludeFromDynamicCompile] public class RealmFileStore { - private readonly RealmContextFactory realmFactory; + private readonly RealmAccess realm; public readonly IResourceStore Store; public readonly Storage Storage; - public RealmFileStore(RealmContextFactory realmFactory, Storage storage) + public RealmFileStore(RealmAccess realm, Storage storage) { - this.realmFactory = realmFactory; + this.realm = realm; Storage = storage.GetStorageForDirectory(@"files"); Store = new StorageBackedResourceStore(Storage); @@ -92,7 +92,7 @@ namespace osu.Game.Stores int removedFiles = 0; // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. - realmFactory.Write(realm => + realm.Write(realm => { // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) var files = realm.All().ToList(); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 3d6240bc98..e6528a83bd 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -77,12 +77,12 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(GameplayClock clock, CancellationToken? cancellationToken, GameHost host, RealmContextFactory realmContextFactory) + private void load(GameplayClock clock, CancellationToken? cancellationToken, GameHost host, RealmAccess realm) { if (clock != null) Clock = clock; - dependencies.Cache(new TextureStore(host.CreateTextureLoaderStore(new RealmFileStore(realmContextFactory, host.Storage).Store), false, scaleAdjust: 1)); + dependencies.Cache(new TextureStore(host.CreateTextureLoaderStore(new RealmFileStore(realm, host.Storage).Store), false, scaleAdjust: 1)); foreach (var layer in Storyboard.Layers) { diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 10cb210f4d..f7e154b5e7 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Beatmaps public IResourceStore Files => userSkinResourceStore; public new IResourceStore Resources => base.Resources; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => null; - RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; + RealmAccess IStorageResourceProvider.RealmAccess => null; #endregion diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 6cc009514d..a6cac0ec86 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual working = CreateWorkingBeatmap(Ruleset.Value); if (IsolateSavingFromDatabase) - Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); } protected override void LoadComplete() @@ -126,14 +126,14 @@ namespace osu.Game.Tests.Visual { public WorkingBeatmap TestBeatmap; - public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap) - : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) + public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap) + : 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(storage, contextFactory, rulesets, onlineLookupQueue); + return new TestBeatmapModelManager(storage, realm, rulesets, onlineLookupQueue); } protected override WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) @@ -157,8 +157,8 @@ namespace osu.Game.Tests.Visual internal class TestBeatmapModelManager : BeatmapModelManager { - public TestBeatmapModelManager(Storage storage, RealmContextFactory databaseContextFactory, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) - : base(databaseContextFactory, storage, beatmapOnlineLookupQueue) + public TestBeatmapModelManager(Storage storage, RealmAccess databaseAccess, RulesetStore rulesetStore, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) + : base(databaseAccess, storage, beatmapOnlineLookupQueue) { } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index da8af49158..33dd1d45b8 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -75,9 +75,9 @@ namespace osu.Game.Tests.Visual /// /// In interactive runs (ie. VisualTests) this will use the user's database if is not set to true. /// - protected RealmContextFactory ContextFactory => contextFactory.Value; + protected RealmAccess Access => contextFactory.Value; - private Lazy contextFactory; + private Lazy contextFactory; /// /// Whether a fresh storage should be initialised per test (method) run. @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual Resources = parent.Get().Resources; - contextFactory = new Lazy(() => new RealmContextFactory(LocalStorage, "client")); + contextFactory = new Lazy(() => new RealmAccess(LocalStorage, "client")); RecycleLocalStorage(false); diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index a080f47d66..cd675e467b 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual public IResourceStore Files => null; public new IResourceStore Resources => base.Resources; public IResourceStore CreateTextureLoaderStore(IResourceStore underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); - RealmContextFactory IStorageResourceProvider.RealmContextFactory => null; + RealmAccess IStorageResourceProvider.RealmAccess => null; #endregion From f30894840c709505464cc2b903f4a8870feb7fd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 20:11:36 +0900 Subject: [PATCH 57/85] Update terminology to realm "instance" rather than "context" This matches the terminology used by realm themselves, which feels better. --- osu.Game.Tests/Database/FileStoreTests.cs | 8 +- .../TestSceneChangeAndUseGameplayBindings.cs | 2 +- osu.Game/Database/RealmAccess.cs | 131 ++++++++---------- osu.Game/Stores/RealmArchiveModelManager.cs | 2 +- 4 files changed, 67 insertions(+), 76 deletions(-) diff --git a/osu.Game.Tests/Database/FileStoreTests.cs b/osu.Game.Tests/Database/FileStoreTests.cs index 008aa3d833..98b0ed99b5 100644 --- a/osu.Game.Tests/Database/FileStoreTests.cs +++ b/osu.Game.Tests/Database/FileStoreTests.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + var realm = realmAccess.Realm; var files = new RealmFileStore(realmAccess, storage); var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 }); @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + var realm = realmAccess.Realm; var files = new RealmFileStore(realmAccess, storage); var testData = new MemoryStream(new byte[] { 0, 1, 2, 3 }); @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + 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)); @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realmAccess, storage) => { - var realm = realmAccess.Context; + var realm = realmAccess.Realm; var files = new RealmFileStore(realmAccess, storage); var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm)); diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs index 7cb29895eb..bfcefdbbfe 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneChangeAndUseGameplayBindings.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Navigation .ChildrenOfType().SingleOrDefault(); private RealmKeyBinding firstOsuRulesetKeyBindings => Game.Dependencies - .Get().Context + .Get().Realm .All() .AsEnumerable() .First(k => k.RulesetName == "osu" && k.ActionInt == 0); diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2f397639c9..66b4edbe84 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -59,9 +59,9 @@ namespace osu.Game.Database /// /// Lock object which is held during sections, blocking context creation during blocking periods. /// - private readonly SemaphoreSlim contextCreationLock = new SemaphoreSlim(1); + private readonly SemaphoreSlim realmCreationLock = new SemaphoreSlim(1); - private readonly ThreadLocal currentThreadCanCreateContexts = new ThreadLocal(); + private readonly ThreadLocal currentThreadCanCreateRealmInstances = new ThreadLocal(); /// /// Holds a map of functions registered via and and a coinciding action which when triggered, @@ -81,35 +81,35 @@ namespace osu.Game.Database /// private readonly Dictionary, Action> notificationsResetMap = new Dictionary, Action>(); - private static readonly GlobalStatistic contexts_created = GlobalStatistics.Get(@"Realm", @"Contexts (Created)"); + private static readonly GlobalStatistic realm_instances_created = GlobalStatistics.Get(@"Realm", @"Instances (Created)"); - private readonly object contextLock = new object(); + private readonly object realmLock = new object(); - private Realm? context; + private Realm? updateRealm; - public Realm Context => ensureUpdateContext(); + public Realm Realm => ensureUpdateRealm(); - private Realm ensureUpdateContext() + private Realm ensureUpdateRealm() { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); + throw new InvalidOperationException(@$"Use {nameof(getRealmInstance)} when performing realm operations from a non-update thread"); - lock (contextLock) + lock (realmLock) { - if (context == null) + if (updateRealm == null) { - context = createContext(); - Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}"); + updateRealm = getRealmInstance(); + + 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(context != null); + Debug.Assert(updateRealm != null); - // creating a context will ensure our schema is up-to-date and migrated. - return context; + return updateRealm; } } @@ -118,7 +118,7 @@ namespace osu.Game.Database private static readonly ThreadLocal current_thread_subscriptions_allowed = new ThreadLocal(); /// - /// Construct a new instance of a realm context factory. + /// Construct a new instance. /// /// The game storage which will be used to create the realm backing file. /// The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified. @@ -137,7 +137,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) @@ -153,7 +153,7 @@ namespace osu.Game.Database private void cleanupPendingDeletions() { - using (var realm = createContext()) + using (var realm = getRealmInstance()) using (var transaction = realm.BeginWrite()) { var pendingDeleteScores = realm.All().Where(s => s.DeletePending); @@ -201,34 +201,28 @@ namespace osu.Game.Database /// /// Run work on realm with a return value. /// - /// - /// Handles correct context management automatically. - /// /// The work to run. /// The return type. public T Run(Func action) { if (ThreadSafety.IsUpdateThread) - return action(Context); + return action(Realm); - using (var realm = createContext()) + using (var realm = getRealmInstance()) return action(realm); } /// /// Run work on realm. /// - /// - /// Handles correct context management automatically. - /// /// The work to run. public void Run(Action action) { if (ThreadSafety.IsUpdateThread) - action(Context); + action(Realm); else { - using (var realm = createContext()) + using (var realm = getRealmInstance()) action(realm); } } @@ -236,17 +230,14 @@ namespace osu.Game.Database /// /// Write changes to realm. /// - /// - /// Handles correct context management and transaction committing automatically. - /// /// The work to run. public void Write(Action action) { if (ThreadSafety.IsUpdateThread) - Context.Write(action); + Realm.Write(action); else { - using (var realm = createContext()) + using (var realm = getRealmInstance()) realm.Write(action); } } @@ -257,10 +248,10 @@ namespace osu.Game.Database /// /// This adds osu! specific thread and managed state safety checks on top of . /// - /// In addition to the documented realm behaviour, we have the additional requirement of handling subscriptions over potential context loss. + /// 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 context loss, a callback with an empty collection and null will be invoked. - /// - On context revival, a standard initial realm callback will arrive, with null and an up-to-date collection. + /// - On recycle start, a callback with an empty collection and null will be invoked. + /// - On recycle end, a standard initial realm callback will arrive, with null and an up-to-date collection. /// /// The to observe for changes. /// Type of the elements in the list. @@ -276,7 +267,7 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread."); - lock (contextLock) + lock (realmLock) { Func action = realm => query(realm).QueryAsyncWithNotifications(callback); @@ -287,7 +278,7 @@ namespace osu.Game.Database } /// - /// Run work on realm that will be run every time the update thread realm context gets recycled. + /// Run work on realm that will be run every time the update thread realm instance gets recycled. /// /// The work to run. Return value should be an from QueryAsyncWithNotifications, or an to clean up any bindings. /// An which should be disposed to unsubscribe any inner subscription. @@ -311,7 +302,7 @@ namespace osu.Game.Database void unsubscribe() { - lock (contextLock) + lock (realmLock) { if (customSubscriptionsResetMap.TryGetValue(action, out var unsubscriptionAction)) { @@ -328,12 +319,12 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - lock (contextLock) + lock (realmLock) { - // Retrieve context outside of flag update to ensure that the context is constructed, + // 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 = Context; + var realm = Realm; Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); @@ -344,12 +335,12 @@ namespace osu.Game.Database } /// - /// Unregister all subscriptions when the realm context is to be recycled. - /// Subscriptions will still remain and will be re-subscribed when the realm context returns. + /// Unregister all subscriptions when the realm instance is to be recycled. + /// Subscriptions will still remain and will be re-subscribed when the realm instance returns. /// private void unregisterAllSubscriptions() { - lock (contextLock) + lock (realmLock) { foreach (var action in notificationsResetMap.Values) action(); @@ -362,7 +353,7 @@ namespace osu.Game.Database } } - private Realm createContext() + private Realm getRealmInstance() { if (isDisposed) throw new ObjectDisposedException(nameof(RealmAccess)); @@ -371,20 +362,20 @@ namespace osu.Game.Database try { - if (!currentThreadCanCreateContexts.Value) + if (!currentThreadCanCreateRealmInstances.Value) { - contextCreationLock.Wait(); - currentThreadCanCreateContexts.Value = true; + realmCreationLock.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()); } @@ -392,8 +383,8 @@ namespace osu.Game.Database { if (tookSemaphoreLock) { - contextCreationLock.Release(); - currentThreadCanCreateContexts.Value = false; + realmCreationLock.Release(); + currentThreadCanCreateRealmInstances.Value = false; } } } @@ -582,7 +573,7 @@ namespace osu.Game.Database } /// - /// Flush any active contexts and block any further writes. + /// Flush any active realm instances and block any further writes. /// /// /// This should be used in places we need to ensure no ongoing reads/writes are occurring with realm. @@ -598,14 +589,14 @@ namespace osu.Game.Database try { - contextCreationLock.Wait(); + realmCreationLock.Wait(); - lock (contextLock) + lock (realmLock) { - if (context == null) + if (updateRealm == null) { - // null context means the update thread has not yet retrieved its context. - // we don't need to worry about reviving the update context in this case, so don't bother with the SynchronizationContext. + // 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 @@ -620,8 +611,8 @@ namespace osu.Game.Database Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); - context?.Dispose(); - context = null; + updateRealm?.Dispose(); + updateRealm = null; } const int sleep_length = 200; @@ -648,17 +639,17 @@ namespace osu.Game.Database } catch { - contextCreationLock.Release(); + realmCreationLock.Release(); throw; } return new InvokeOnDisposal(this, factory => { - factory.contextCreationLock.Release(); + factory.realmCreationLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); // Post back to the update thread to revive any subscriptions. - syncContext?.Post(_ => ensureUpdateContext(), null); + syncContext?.Post(_ => ensureUpdateRealm(), null); }); } @@ -669,16 +660,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(); + realmCreationLock.Wait(); + realmCreationLock.Dispose(); isDisposed = true; } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 01eb90d6e8..00cd1c2958 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -45,7 +45,7 @@ namespace osu.Game.Stores // This method should be removed as soon as all the surrounding pieces support non-detached operations. if (!item.IsManaged) { - var managed = Access.Context.Find(item.ID); + var managed = Access.Realm.Find(item.ID); managed.Realm.Write(() => operation(managed)); item.Files.Clear(); From 6f4c337a5696ef2e51c10511ceeed873cd0834b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 20:52:27 +0900 Subject: [PATCH 58/85] Fix a failed `BlockAllOperations` leaving update realm in unretrieved state If the operation timed out on.. ```csharp throw new TimeoutException(@"Took too long to acquire lock"); ``` ..from an update thread, it would not restore the update context. The next call would then fail on the assert that ensures a non-null context in such cases. Can add test coverage if required. --- osu.Game/Database/RealmAccess.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 66b4edbe84..64063664eb 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -595,8 +595,8 @@ namespace osu.Game.Database { 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. + // null context means the update thread has not yet retrieved its context. + // we don't need to worry about reviving the update context in this case, so don't bother with the SynchronizationContext. Debug.Assert(!ThreadSafety.IsUpdateThread); } else @@ -639,18 +639,19 @@ namespace osu.Game.Database } catch { - realmCreationLock.Release(); + restoreOperation(); throw; } - return new InvokeOnDisposal(this, factory => - { - factory.realmCreationLock.Release(); - Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + return new InvokeOnDisposal(restoreOperation); + void restoreOperation() + { + Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); + realmCreationLock.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 From a7c0d507cefa16343475f5aa73a23d666b6b7446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:44:25 +0100 Subject: [PATCH 59/85] Rename flashlight settings to be more accurate --- .../Mods/CatchModFlashlight.cs | 20 +++++++++---------- .../Mods/ManiaModFlashlight.cs | 20 +++++++++---------- .../Mods/OsuModFlashlight.cs | 20 +++++++++---------- .../Mods/TaikoModFlashlight.cs | 20 +++++++++---------- osu.Game/Rulesets/Mods/ModFlashlight.cs | 8 ++++---- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index d48382a9ee..e7335dcc34 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -16,15 +16,8 @@ namespace osu.Game.Rulesets.Catch.Mods { public override double ScoreMultiplier => 1.12; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0.4f, MaxValue = 1.7f, @@ -33,9 +26,16 @@ namespace osu.Game.Rulesets.Catch.Mods 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 + }; + protected virtual float DefaultFlashlightSize => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); private CatchPlayfield playfield; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index eb3f60edce..f89c131fea 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -17,15 +17,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = false, - Value = false - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0f, MaxValue = 4.5f, @@ -34,9 +27,16 @@ namespace osu.Game.Rulesets.Mania.Mods Precision = 0.1f }; + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public override BindableBool ComboBasedSize { get; } = new BindableBool + { + Default = false, + Value = false + }; + protected virtual float DefaultFlashlightSize => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => new ManiaFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); private class ManiaFlashlight : Flashlight { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 6a9d199c54..bc915591d0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -30,15 +30,8 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = default_follow_delay, }; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0.5f, MaxValue = 2f, @@ -47,11 +40,18 @@ namespace osu.Game.Rulesets.Osu.Mods 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 + }; + protected virtual float DefaultFlashlightSize => 180; private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ChangeRadius.Value, InitialRadius.Value, FollowDelay.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, FollowDelay.Value, DefaultFlashlightSize); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 8de7c859c4..48e56c8784 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -17,15 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => 1.12; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public override BindableBool ChangeRadius { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public override BindableNumber InitialRadius { get; } = new BindableNumber + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public override BindableNumber SizeMultiplier { get; } = new BindableNumber { MinValue = 0, MaxValue = 1.66f, @@ -34,9 +27,16 @@ namespace osu.Game.Rulesets.Taiko.Mods 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 + }; + protected virtual float DefaultFlashlightSize => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ChangeRadius.Value, InitialRadius.Value, DefaultFlashlightSize); + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); private TaikoPlayfield playfield; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 531ee92b7a..d3998bcad4 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; - [SettingSource("Change radius based on combo", "Decrease the flashlight radius as combo increases.")] - public abstract BindableBool ChangeRadius { get; } + [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] + public abstract BindableNumber SizeMultiplier { get; } - [SettingSource("Initial radius", "Initial radius of the flashlight area.")] - public abstract BindableNumber InitialRadius { get; } + [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] + public abstract BindableBool ComboBasedSize { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From 5874475dffbc5ba6d1bf620d07e026360858a3a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:46:54 +0100 Subject: [PATCH 60/85] Extract `DefaultFlashlightSize` to base flashlight class --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 2 +- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- osu.Game/Rulesets/Mods/ModFlashlight.cs | 6 ++++++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e7335dcc34..4c5dcf84d2 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Mods Value = true }; - protected virtual float DefaultFlashlightSize => 350; + public override float DefaultFlashlightSize => 350; public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index f89c131fea..03be00c5db 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Mods Value = false }; - protected virtual float DefaultFlashlightSize => 50; + public override float DefaultFlashlightSize => 50; public override Flashlight CreateFlashlight() => new ManiaFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index bc915591d0..f344bb017e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods Value = true }; - protected virtual float DefaultFlashlightSize => 180; + public override float DefaultFlashlightSize => 180; private OsuFlashlight flashlight; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 48e56c8784..576fbb65a0 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Mods Value = true }; - protected virtual float DefaultFlashlightSize => 250; + public override float DefaultFlashlightSize => 250; public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index d3998bcad4..f2fd86d549 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -38,6 +38,12 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public abstract BindableBool ComboBasedSize { get; } + + /// + /// The default size of the flashlight in ruleset-appropriate dimensions. + /// and will apply their adjustments on top of this size. + /// + public abstract float DefaultFlashlightSize { get; } } public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor From a227af75edb70a847aac468c47371bacc4d3d49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:55:24 +0100 Subject: [PATCH 61/85] Simplify flashlight parameter passing flow --- .../Mods/CatchModFlashlight.cs | 10 +++---- .../Mods/ManiaModFlashlight.cs | 10 +++---- .../Mods/OsuModFlashlight.cs | 19 +++++------- .../Mods/TaikoModFlashlight.cs | 8 ++--- osu.Game/Rulesets/Mods/ModFlashlight.cs | 30 +++++++++---------- 5 files changed, 37 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 4c5dcf84d2..6bd914de33 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override float DefaultFlashlightSize => 350; - public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield); private CatchPlayfield playfield; @@ -49,11 +49,11 @@ namespace osu.Game.Rulesets.Catch.Mods { private readonly CatchPlayfield playfield; - public CatchFlashlight(CatchPlayfield playfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public CatchFlashlight(CatchModFlashlight modFlashlight, CatchPlayfield playfield) + : base(modFlashlight) { this.playfield = playfield; - FlashlightSize = new Vector2(0, GetRadiusFor(0)); + FlashlightSize = new Vector2(0, GetSizeFor(0)); } protected override void Update() @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Mods protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 03be00c5db..def3f8b274 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -36,16 +36,16 @@ namespace osu.Game.Rulesets.Mania.Mods public override float DefaultFlashlightSize => 50; - public override Flashlight CreateFlashlight() => new ManiaFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => new ManiaFlashlight(this); private class ManiaFlashlight : Flashlight { private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); - public ManiaFlashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public ManiaFlashlight(ManiaModFlashlight modFlashlight) + : base(modFlashlight) { - FlashlightSize = new Vector2(DrawWidth, GetRadiusFor(0)); + FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0)); AddLayout(flashlightProperties); } @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Mods protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "RectangularFlashlight"; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index f344bb017e..b4eff57c55 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuFlashlight flashlight; - public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(ComboBasedSize.Value, SizeMultiplier.Value, FollowDelay.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { @@ -61,17 +61,14 @@ namespace osu.Game.Rulesets.Osu.Mods private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { - public double FollowDelay { private get; set; } + private readonly double followDelay; - //public float InitialRadius { private get; set; } - public bool ChangeRadius { private get; set; } - - public OsuFlashlight(bool isRadiusBasedOnCombo, float initialRadius, double followDelay, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public OsuFlashlight(OsuModFlashlight modFlashlight) + : base(modFlashlight) { - FollowDelay = followDelay; + followDelay = modFlashlight.FollowDelay.Value; - FlashlightSize = new Vector2(0, GetRadiusFor(0)); + FlashlightSize = new Vector2(0, GetSizeFor(0)); } public void OnSliderTrackingChange(ValueChangedEvent e) @@ -86,14 +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); } protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetRadiusFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 576fbb65a0..84444dded9 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public override float DefaultFlashlightSize => 250; - public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield, ComboBasedSize.Value, SizeMultiplier.Value, DefaultFlashlightSize); + protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield); private TaikoPlayfield playfield; @@ -51,8 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Mods private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; - public TaikoFlashlight(TaikoPlayfield taikoPlayfield, bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) - : base(isRadiusBasedOnCombo, initialRadius, defaultFlashlightSize) + public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield) + : base(modFlashlight) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = getSizeFor(0); @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Mods private Vector2 getSizeFor(int combo) { // Preserve flashlight size through the playfield's aspect adjustment. - return new Vector2(0, GetRadiusFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); + return new Vector2(0, GetSizeFor(combo) * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); } protected override void OnComboChange(ValueChangedEvent e) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index f2fd86d549..e6487c6b29 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Mods flashlight.Breaks = drawableRuleset.Beatmap.Breaks; } - public abstract Flashlight CreateFlashlight(); + protected abstract Flashlight CreateFlashlight(); public abstract class Flashlight : Drawable { @@ -102,17 +102,15 @@ namespace osu.Game.Rulesets.Mods public List Breaks; - public readonly bool IsRadiusBasedOnCombo; + private readonly float defaultFlashlightSize; + private readonly float sizeMultiplier; + private readonly bool comboBasedSize; - public readonly float InitialRadius; - - public readonly float DefaultFlashlightSize; - - protected Flashlight(bool isRadiusBasedOnCombo, float initialRadius, float defaultFlashlightSize) + protected Flashlight(ModFlashlight modFlashlight) { - IsRadiusBasedOnCombo = isRadiusBasedOnCombo; - InitialRadius = initialRadius; - DefaultFlashlightSize = defaultFlashlightSize; + defaultFlashlightSize = modFlashlight.DefaultFlashlightSize; + sizeMultiplier = modFlashlight.SizeMultiplier.Value; + comboBasedSize = modFlashlight.ComboBasedSize.Value; } [BackgroundDependencyLoader] @@ -146,17 +144,19 @@ namespace osu.Game.Rulesets.Mods protected abstract string FragmentShader { get; } - protected float GetRadiusFor(int combo) + protected float GetSizeFor(int combo) { - if (IsRadiusBasedOnCombo) + float size = defaultFlashlightSize * sizeMultiplier; + + if (comboBasedSize) { if (combo > 200) - return InitialRadius * 0.8f * DefaultFlashlightSize; + size *= 0.8f; else if (combo > 100) - return InitialRadius * 0.9f * DefaultFlashlightSize; + size *= 0.9f; } - return InitialRadius * DefaultFlashlightSize; + return size; } private Vector2 flashlightPosition; From 4a13c93ca7e98c89ba5938c89c1e00a2856c08c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 20:56:59 +0100 Subject: [PATCH 62/85] Disallow zero size multiplier in flashlight implementations --- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index def3f8b274..60de063b24 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0f, + MinValue = 0.1f, MaxValue = 4.5f, Default = 1f, Value = 1f, diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 84444dded9..bdb30e9ded 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0, + MinValue = 0.1f, MaxValue = 1.66f, Default = 1f, Value = 1f, From 2375420d4cbce1372fd2848b7de799bee2f281b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 24 Jan 2022 21:16:10 +0100 Subject: [PATCH 63/85] Tweak allowable ranges of size multiplier --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 4 ++-- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 4 ++-- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 6bd914de33..2d92c925d7 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Catch.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0.4f, - MaxValue = 1.7f, + MinValue = 0.5f, + MaxValue = 1.5f, Default = 1f, Value = 1f, Precision = 0.1f diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 60de063b24..1ee4ea12e3 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0.1f, - MaxValue = 4.5f, + MinValue = 0.5f, + MaxValue = 3f, Default = 1f, Value = 1f, Precision = 0.1f diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index bdb30e9ded..fb07c687bb 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Taiko.Mods [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableNumber SizeMultiplier { get; } = new BindableNumber { - MinValue = 0.1f, - MaxValue = 1.66f, + MinValue = 0.5f, + MaxValue = 1.5f, Default = 1f, Value = 1f, Precision = 0.1f From 2d34831b5fd0383c75cf1c4cdc5d49a9b256d892 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 12:54:38 +0900 Subject: [PATCH 64/85] Fix `TestMigration` failing due to changes in realm migration logic Fixes failures as seen at https://github.com/ppy/osu/runs/4927916031?check_suite_focus=true. --- .../NonVisual/CustomDataDirectoryTest.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 61ef31e07e..834930a05e 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -142,19 +142,28 @@ namespace osu.Game.Tests.NonVisual Assert.That(osuStorage, Is.Not.Null); + // In the following tests, realm files are ignored as + // - in the case of checking the source, interacting with the pipe files (client.realm.note) may + // lead to unexpected behaviour. + // - in the case of checking the destination, the files may have already been recreated by the game + // as part of the standard migration flow. + foreach (string file in osuStorage.IgnoreFiles) { - // avoid touching realm files which may be a pipe and break everything. - // this is also done locally inside OsuStorage via the IgnoreFiles list. - if (file.EndsWith(".ini", StringComparison.Ordinal)) + if (!file.Contains("realm", StringComparison.Ordinal)) + { Assert.That(File.Exists(Path.Combine(originalDirectory, file))); - Assert.That(storage.Exists(file), Is.False); + Assert.That(storage.Exists(file), Is.False, () => $"{file} exists in destination when it was expected to be ignored"); + } } foreach (string dir in osuStorage.IgnoreDirectories) { - Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir))); - Assert.That(storage.ExistsDirectory(dir), Is.False); + if (!dir.Contains("realm", StringComparison.Ordinal)) + { + Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir))); + Assert.That(storage.Exists(dir), Is.False, () => $"{dir} exists in destination when it was expected to be ignored"); + } } Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}")); From 3e5c9e843606647561eb35541459386ecf54da20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 12:58:15 +0900 Subject: [PATCH 65/85] Fix cases of `Access` instead of `Realm` --- .../TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 8 ++++---- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 6 +++--- .../Collections/TestSceneManageCollectionsDialog.cs | 6 +++--- .../Visual/Multiplayer/QueueModeTestScene.cs | 6 +++--- .../Multiplayer/TestSceneDrawableRoomPlaylist.cs | 6 +++--- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 6 +++--- .../TestSceneMultiplayerMatchSongSelect.cs | 6 +++--- .../TestSceneMultiplayerMatchSubScreen.cs | 6 +++--- .../Multiplayer/TestSceneMultiplayerPlaylist.cs | 6 +++--- .../Multiplayer/TestSceneMultiplayerQueueList.cs | 6 +++--- .../Multiplayer/TestSceneMultiplayerReadyButton.cs | 6 +++--- .../TestSceneMultiplayerSpectateButton.cs | 6 +++--- .../Multiplayer/TestScenePlaylistsSongSelect.cs | 6 +++--- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 6 +++--- .../Playlists/TestScenePlaylistsRoomCreation.cs | 6 +++--- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 8 ++++---- .../Visual/SongSelect/TestSceneFilterControl.cs | 6 +++--- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 6 +++--- .../Visual/SongSelect/TestSceneTopLocalRank.cs | 8 ++++---- .../UserInterface/TestSceneDeleteLocalScore.cs | 8 ++++---- osu.Game/Beatmaps/BeatmapModelManager.cs | 4 ++-- osu.Game/Scoring/ScoreModelManager.cs | 2 +- osu.Game/Skinning/SkinModelManager.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 2 +- osu.Game/Stores/RealmArchiveModelImporter.cs | 12 ++++++------ osu.Game/Stores/RealmArchiveModelManager.cs | 6 +++--- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 6 +++--- 28 files changed, 82 insertions(+), 82 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index e35e4d9b15..144cbe15c3 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -45,8 +45,8 @@ namespace osu.Game.Tests.Online [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); Dependencies.CacheAs(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API, host)); } @@ -60,8 +60,8 @@ namespace osu.Game.Tests.Online testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; - Access.Write(r => r.RemoveAll()); - Access.Write(r => r.RemoveAll()); + Realm.Write(r => r.RemoveAll()); + Realm.Write(r => r.RemoveAll()); selectedItem.Value = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index fbfa7eda6a..40e7c0a844 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -47,10 +47,10 @@ namespace osu.Game.Tests.Visual.Background [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, 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(Access); + Dependencies.Cache(Realm); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index fd0645a1e9..d4c13059da 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 07b2bdcba3..4cd19b53a4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 0bf1d60ac5..30ac1302aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -43,9 +43,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 063d886729..3f151a0ae8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -61,9 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 8c79c468d7..7e3c9722cf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + 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(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 77e2c9c714..9d14d80d07 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 4270818b1a..d970ab6c34 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -33,9 +33,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 56e64292c6..d83421ee3a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -38,9 +38,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index afb60b62aa..9867e5225e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 79b29f0eca..42ae279667 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 6ed57e9899..d7a0885c95 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -34,9 +34,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + 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(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index bd95b297d4..73c67d26d9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 716e3a535d..d397b37d05 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Playlists [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, rulesets, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); } [SetUpSteps] diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2f5594379b..5e977d075d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -42,10 +42,10 @@ namespace osu.Game.Tests.Visual.SongSelect { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(Access)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Access, Scheduler)); - Dependencies.Cache(Access); + dependencies.Cache(rulesetStore = new RulesetStore(Realm)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(Realm); return dependencies; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 4868a4a075..b384061531 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, Audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index d8a39dda01..0dc4556ed9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -47,9 +47,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(Access)); - Dependencies.Cache(Access); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Access, 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()); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index a0657ffdf6..8e5f76a2eb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -28,10 +28,10 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RulesetStore(Access)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Access, Scheduler)); - Dependencies.Cache(Access); + Dependencies.Cache(rulesets = new RulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 2278d3f8bf..94ce85ef38 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -87,10 +87,10 @@ namespace osu.Game.Tests.Visual.UserInterface { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RulesetStore(Access)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Access, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Access, Scheduler)); - Dependencies.Cache(Access); + dependencies.Cache(rulesetStore = new RulesetStore(Realm)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(Realm); var imported = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 167d77d6f6..e8104f2ecb 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -98,12 +98,12 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo? QueryBeatmap(Expression> query) { - return Access.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return Realm.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); } public void Update(BeatmapSetInfo item) { - Access.Write(realm => + Realm.Write(realm => { var existing = realm.Find(item.ID); item.CopyChangesToRealm(existing); diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 2147ff1ba1..59102360f9 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -74,7 +74,7 @@ namespace osu.Game.Scoring public override bool IsAvailableLocally(ScoreInfo model) { - return Access.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); + return Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); } } } diff --git a/osu.Game/Skinning/SkinModelManager.cs b/osu.Game/Skinning/SkinModelManager.cs index c93cdb17dd..0af31100a9 100644 --- a/osu.Game/Skinning/SkinModelManager.cs +++ b/osu.Game/Skinning/SkinModelManager.cs @@ -205,7 +205,7 @@ namespace osu.Game.Skinning private void populateMissingHashes() { - Access.Run(realm => + Realm.Run(realm => { var skinsWithoutHashes = realm.All().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray(); diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 3d241e795c..a6f20c8d4f 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -165,7 +165,7 @@ namespace osu.Game.Stores public override bool IsAvailableLocally(BeatmapSetInfo model) { - return Access.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); + return Realm.Run(realm => realm.All().Any(b => b.OnlineID == model.OnlineID)); } public override string HumanisedModelName => "beatmap"; diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 23a860791e..d9ca3f50a3 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Stores protected readonly RealmFileStore Files; - protected readonly RealmAccess Access; + protected readonly RealmAccess Realm; /// /// Fired when the user requests to view the resulting import. @@ -73,7 +73,7 @@ namespace osu.Game.Stores protected RealmArchiveModelImporter(Storage storage, RealmAccess realm) { - Access = realm; + Realm = realm; Files = new RealmFileStore(realm, storage); } @@ -320,7 +320,7 @@ namespace osu.Game.Stores /// An optional cancellation token. public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - return Access.Run(realm => + return Realm.Run(realm => { cancellationToken.ThrowIfCancellationRequested(); @@ -352,7 +352,7 @@ namespace osu.Game.Stores transaction.Commit(); } - return Task.FromResult((ILive?)existing.ToLive(Access)); + return Task.FromResult((ILive?)existing.ToLive(Realm)); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -387,7 +387,7 @@ namespace osu.Game.Stores existing.DeletePending = false; transaction.Commit(); - return Task.FromResult((ILive?)existing.ToLive(Access)); + return Task.FromResult((ILive?)existing.ToLive(Realm)); } LogForModel(item, @"Found existing but failed re-use check."); @@ -413,7 +413,7 @@ namespace osu.Game.Stores throw; } - return Task.FromResult((ILive?)item.ToLive(Access)); + return Task.FromResult((ILive?)item.ToLive(Realm)); }); } diff --git a/osu.Game/Stores/RealmArchiveModelManager.cs b/osu.Game/Stores/RealmArchiveModelManager.cs index 00cd1c2958..57e51b79aa 100644 --- a/osu.Game/Stores/RealmArchiveModelManager.cs +++ b/osu.Game/Stores/RealmArchiveModelManager.cs @@ -45,7 +45,7 @@ namespace osu.Game.Stores // This method should be removed as soon as all the surrounding pieces support non-detached operations. if (!item.IsManaged) { - var managed = Access.Realm.Find(item.ID); + var managed = Realm.Realm.Find(item.ID); managed.Realm.Write(() => operation(managed)); item.Files.Clear(); @@ -165,7 +165,7 @@ namespace osu.Game.Stores public bool Delete(TModel item) { - return Access.Run(realm => + return Realm.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); @@ -180,7 +180,7 @@ namespace osu.Game.Stores public void Undelete(TModel item) { - Access.Run(realm => + Realm.Run(realm => { if (!item.IsManaged) item = realm.Find(item.ID); diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index a6cac0ec86..6f751b1736 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual working = CreateWorkingBeatmap(Ruleset.Value); if (IsolateSavingFromDatabase) - Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Access, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); } protected override void LoadComplete() diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 33dd1d45b8..42e96f80ca 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -75,9 +75,9 @@ namespace osu.Game.Tests.Visual /// /// In interactive runs (ie. VisualTests) this will use the user's database if is not set to true. /// - protected RealmAccess Access => contextFactory.Value; + protected RealmAccess Realm => realm.Value; - private Lazy contextFactory; + private Lazy realm; /// /// Whether a fresh storage should be initialised per test (method) run. @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual Resources = parent.Get().Resources; - contextFactory = new Lazy(() => new RealmAccess(LocalStorage, "client")); + realm = new Lazy(() => new RealmAccess(LocalStorage, "client")); RecycleLocalStorage(false); From e23b10e6a5a91ba1d7fd1f746846043c73c0427c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:04:05 +0900 Subject: [PATCH 66/85] Update remaining cases of clashing variable name in `realm.Run(realm..` --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 10 ++-- .../Database/TestRealmKeyBindingStore.cs | 4 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 8 ++-- .../TestSceneDeleteLocalScore.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 34 +++++++------- osu.Game/Database/EFToRealmMigrator.cs | 46 +++++++++---------- osu.Game/Database/RealmLive.cs | 9 ++-- osu.Game/Input/RealmKeyBindingStore.cs | 10 ++-- .../Settings/Sections/Input/KeyBindingRow.cs | 6 +-- osu.Game/Scoring/ScoreManager.cs | 8 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 10 ++-- osu.Game/Skinning/SkinManager.cs | 6 +-- 13 files changed, 77 insertions(+), 80 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 412f86bd1c..bf9467700c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -29,7 +29,7 @@ namespace osu.Game.Benchmarks realm = new RealmAccess(storage, "client"); - realm.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() { - realm.Run(realm => + realm.Run(r => { - var beatmapSet = realm.All().First(); + var beatmapSet = r.All().First(); for (int i = 0; i < ReadsPerFetch; i++) { @@ -119,9 +119,9 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDetachedPropertyRead() { - realm.Run(realm => + realm.Run(r => { - var beatmapSet = realm.All().First().Detach(); + var beatmapSet = r.All().First().Detach(); for (int i = 0; i < ReadsPerFetch; i++) { diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 4b8816f142..7b2d2a1278 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -76,9 +76,9 @@ namespace osu.Game.Tests.Database private int queryCount(GlobalAction? match = null) { - return realm.Run(realm => + return realm.Run(r => { - var results = realm.All(); + var results = r.All(); if (match.HasValue) results = results.Where(k => k.ActionInt == (int)match.Value); return results.Count(); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 13b4af5223..988f429ff5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -42,11 +42,11 @@ namespace osu.Game.Tests.Visual.Ranking { base.LoadComplete(); - realm.Run(realm => + realm.Run(r => { - var beatmapInfo = realm.All() - .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) - .FirstOrDefault(); + var beatmapInfo = r.All() + .Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0) + .FirstOrDefault(); if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 94ce85ef38..55031a9699 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -122,10 +122,10 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void Setup() => Schedule(() => { - realm.Run(realm => + realm.Run(r => { // Due to soft deletions, we can re-use deleted scores between test runs - scoreManager.Undelete(realm.All().Where(s => s.DeletePending).ToList()); + scoreManager.Undelete(r.All().Where(s => s.DeletePending).ToList()); }); leaderboard.Scores = null; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ddc1d054cc..4488301fe5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -119,12 +119,12 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to hide. public void Hide(BeatmapInfo beatmapInfo) { - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { if (!beatmapInfo.IsManaged) - beatmapInfo = realm.Find(beatmapInfo.ID); + beatmapInfo = r.Find(beatmapInfo.ID); beatmapInfo.Hidden = true; transaction.Commit(); @@ -138,12 +138,12 @@ namespace osu.Game.Beatmaps /// The beatmap difficulty to restore. public void Restore(BeatmapInfo beatmapInfo) { - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { if (!beatmapInfo.IsManaged) - beatmapInfo = realm.Find(beatmapInfo.ID); + beatmapInfo = r.Find(beatmapInfo.ID); beatmapInfo.Hidden = false; transaction.Commit(); @@ -153,11 +153,11 @@ namespace osu.Game.Beatmaps public void RestoreAll() { - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { - foreach (var beatmap in realm.All().Where(b => b.Hidden)) + foreach (var beatmap in r.All().Where(b => b.Hidden)) beatmap.Hidden = false; transaction.Commit(); @@ -171,10 +171,10 @@ namespace osu.Game.Beatmaps /// A list of available . public List GetAllUsableBeatmapSets() { - return realm.Run(realm => + return realm.Run(r => { - realm.Refresh(); - return realm.All().Where(b => !b.DeletePending).Detach(); + r.Refresh(); + return r.All().Where(b => !b.DeletePending).Detach(); }); } @@ -240,9 +240,9 @@ namespace osu.Game.Beatmaps public void Delete(Expression>? filter = null, bool silent = false) { - realm.Run(realm => + realm.Run(r => { - var items = realm.All().Where(s => !s.DeletePending && !s.Protected); + var items = r.All().Where(s => !s.DeletePending && !s.Protected); if (filter != null) items = items.Where(filter); @@ -253,7 +253,7 @@ namespace osu.Game.Beatmaps public void UndeleteAll() { - realm.Run(realm => beatmapModelManager.Undelete(realm.All().Where(s => s.DeletePending).ToList())); + realm.Run(r => beatmapModelManager.Undelete(r.All().Where(s => s.DeletePending).ToList())); } public void Undelete(List items, bool silent = false) @@ -312,9 +312,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) { - realm.Run(realm => + realm.Run(r => { - var refetch = realm.Find(importedBeatmap.ID)?.Detach(); + var refetch = r.Find(importedBeatmap.ID)?.Detach(); if (refetch != null) importedBeatmap = refetch; diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index a0787e81e6..349452afce 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -158,11 +158,11 @@ namespace osu.Game.Database int count = existingBeatmapSets.Count(); - realm.Run(realm => + realm.Run(r => { log($"Found {count} beatmaps in EF"); - var transaction = realm.BeginWrite(); + var transaction = r.BeginWrite(); int written = 0; try @@ -172,7 +172,7 @@ namespace osu.Game.Database if (++written % 1000 == 0) { transaction.Commit(); - transaction = realm.BeginWrite(); + transaction = r.BeginWrite(); log($"Migrated {written}/{count} beatmaps..."); } @@ -186,11 +186,11 @@ namespace osu.Game.Database Protected = beatmapSet.Protected, }; - migrateFiles(beatmapSet, realm, realmBeatmapSet); + migrateFiles(beatmapSet, r, realmBeatmapSet); foreach (var beatmap in beatmapSet.Beatmaps) { - var ruleset = realm.Find(beatmap.RulesetInfo.ShortName); + var ruleset = r.Find(beatmap.RulesetInfo.ShortName); var metadata = getBestMetadata(beatmap.Metadata, beatmapSet.Metadata); var realmBeatmap = new BeatmapInfo(ruleset, new BeatmapDifficulty(beatmap.BaseDifficulty), metadata) @@ -225,7 +225,7 @@ namespace osu.Game.Database realmBeatmapSet.Beatmaps.Add(realmBeatmap); } - realm.Add(realmBeatmapSet); + r.Add(realmBeatmapSet); } } finally @@ -280,11 +280,11 @@ namespace osu.Game.Database int count = existingScores.Count(); - realm.Run(realm => + realm.Run(r => { log($"Found {count} scores in EF"); - var transaction = realm.BeginWrite(); + var transaction = r.BeginWrite(); int written = 0; try @@ -294,12 +294,12 @@ namespace osu.Game.Database if (++written % 1000 == 0) { transaction.Commit(); - transaction = realm.BeginWrite(); + transaction = r.BeginWrite(); log($"Migrated {written}/{count} scores..."); } - var beatmap = realm.All().First(b => b.Hash == score.BeatmapInfo.Hash); - var ruleset = realm.Find(score.Ruleset.ShortName); + var beatmap = r.All().First(b => b.Hash == score.BeatmapInfo.Hash); + var ruleset = r.Find(score.Ruleset.ShortName); var user = new RealmUser { OnlineID = score.User.OnlineID, @@ -329,9 +329,9 @@ namespace osu.Game.Database APIMods = score.APIMods, }; - migrateFiles(score, realm, realmScore); + migrateFiles(score, r, realmScore); - realm.Add(realmScore); + r.Add(realmScore); } } finally @@ -369,13 +369,13 @@ namespace osu.Game.Database break; } - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { // only migrate data if the realm database is empty. - // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. - if (!realm.All().Any(s => !s.Protected)) + // note that this cannot be written as: `r.All().All(s => s.Protected)`, because realm does not support `.All()`. + if (!r.All().Any(s => !s.Protected)) { log($"Migrating {existingSkins.Count} skins"); @@ -390,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(); @@ -428,12 +428,12 @@ namespace osu.Game.Database log("Beginning settings migration to realm"); - realm.Run(realm => + realm.Run(r => { - using (var transaction = realm.BeginWrite()) + using (var transaction = r.BeginWrite()) { // only migrate data if the realm database is empty. - if (!realm.All().Any()) + if (!r.All().Any()) { log($"Migrating {existingSettings.Count} settings"); @@ -447,7 +447,7 @@ namespace osu.Game.Database if (string.IsNullOrEmpty(shortName)) continue; - realm.Add(new RealmRulesetSetting + r.Add(new RealmRulesetSetting { Key = dkb.Key, Value = dkb.StringValue, diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 29159fd5be..f06ba1eff9 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -51,10 +51,7 @@ namespace osu.Game.Database return; } - realm.Run(realm => - { - perform(retrieveFromID(realm, ID)); - }); + realm.Run(r => perform(retrieveFromID(r, ID))); } /// @@ -66,9 +63,9 @@ namespace osu.Game.Database if (!IsManaged) return perform(data); - return realm.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)}."); diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index cccd42a9aa..20971ffca5 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -56,21 +56,21 @@ namespace osu.Game.Input /// The rulesets to populate defaults from. public void Register(KeyBindingContainer container, IEnumerable rulesets) { - realm.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().ToList(); + var existingBindings = r.All().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(); diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 91883e4f41..2405618917 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -386,10 +386,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateStoreFromButton(KeyButton button) { - realm.Run(realm => + realm.Run(r => { - var binding = realm.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); - realm.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); + var binding = r.Find(((IHasGuidPrimaryKey)button.KeyBinding).ID); + r.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString); }); } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index e712d170cd..bb26bd3b04 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -51,7 +51,7 @@ namespace osu.Game.Scoring /// The first result for the provided query, or null if no results were found. public ScoreInfo Query(Expression> query) { - return realm.Run(realm => realm.All().FirstOrDefault(query)?.Detach()); + return realm.Run(r => r.All().FirstOrDefault(query)?.Detach()); } /// @@ -254,10 +254,10 @@ namespace osu.Game.Scoring public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - realm.Run(realm => + realm.Run(r => { - var items = realm.All() - .Where(s => !s.DeletePending); + var items = r.All() + .Where(s => !s.DeletePending); if (filter != null) items = items.Where(filter); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 8e0fdea0f8..b02abd2f7a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Select if (!loadedTestBeatmaps) { - realm.Run(realm => loadBeatmapSets(getBeatmapSets(realm))); + realm.Run(r => loadBeatmapSets(getBeatmapSets(r))); } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 3d262f8b97..b53aa6f43c 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -150,12 +150,12 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - realm.Run(realm => + realm.Run(r => { - var scores = realm.All() - .AsEnumerable() - // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). - .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); + var scores = r.All() + .AsEnumerable() + // TODO: update to use a realm filter directly (or at least figure out the beatmap part to reduce scope). + .Where(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID); if (filterMods && !mods.Value.Any()) { diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 66956325da..22fb8ce86f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -289,10 +289,10 @@ namespace osu.Game.Skinning public void Delete([CanBeNull] Expression> filter = null, bool silent = false) { - realm.Run(realm => + realm.Run(r => { - var items = realm.All() - .Where(s => !s.Protected && !s.DeletePending); + var items = r.All() + .Where(s => !s.Protected && !s.DeletePending); if (filter != null) items = items.Where(filter); From d7342880f5a099e3260695bd04741a61864ff921 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:09:47 +0900 Subject: [PATCH 67/85] Update remaining cases of clashes with `realm.Write` and `realm.RegisterForNotifications` --- .../Database/TestRealmKeyBindingStore.cs | 8 ++++---- osu.Game/Database/EFToRealmMigrator.cs | 10 +++++----- .../Input/Bindings/DatabasedKeyBindingContainer.cs | 2 +- osu.Game/Online/BeatmapDownloadTracker.cs | 2 +- .../Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 2 +- .../Rulesets/Configuration/RulesetConfigManager.cs | 4 ++-- osu.Game/Rulesets/RulesetStore.cs | 6 +++--- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++--- osu.Game/Screens/Select/Carousel/TopLocalRank.cs | 14 +++++++------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 6 +++--- osu.Game/Skinning/SkinManager.cs | 6 +++--- osu.Game/Stores/RealmFileStore.cs | 6 +++--- 15 files changed, 39 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 7b2d2a1278..891801865f 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -60,11 +60,11 @@ namespace osu.Game.Tests.Database KeyBindingContainer testContainer = new TestKeyBindingContainer(); // Add some excess bindings for an action which only supports 1. - realm.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)); diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 349452afce..adf91e4a41 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -101,15 +101,15 @@ namespace osu.Game.Database { using (var ef = efContextFactory.Get()) { - realm.Write(realm => + 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. - realm.RemoveAll(); - realm.RemoveAll(); - realm.RemoveAll(); - realm.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); + r.RemoveAll(); }); migrateSettings(ef); diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index d54c049c99..3e4a9759a3 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realm.RegisterForNotifications(realm => queryRealmKeyBindings(), (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(r => 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. diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index f54dc30620..9f795f007a 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -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 = realm.RegisterForNotifications(realm => realm.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index c562695ac1..c67cbade6a 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -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 = realm.RegisterForNotifications(realm => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(), (items, changes, ___) => { if (changes == null) return; diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 81dfc811a4..d7e31c8a59 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online Downloader.DownloadBegan += downloadBegan; Downloader.DownloadFailed += downloadFailed; - realmSubscription = realm.RegisterForNotifications(realm => realm.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || s.Hash == TrackedItem.Hash) && !s.DeletePending), (items, changes, ___) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0f8e390ebf..9dc55d24dd 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); - beatmapSubscription = realm.RegisterForNotifications(realm => queryRealmBeatmapSets(), beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(r => queryRealmBeatmapSets(), beatmapsChanged); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index af3fd5c9bf..8ab296c0a8 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realm.RegisterForNotifications(realm => queryRealmSkins(), (sender, changes, error) => + 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. diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index cfa20e0b87..30bb95ba72 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -56,11 +56,11 @@ namespace osu.Game.Rulesets.Configuration pendingWrites.Clear(); } - realm?.Write(realm => + realm?.Write(r => { foreach (var c in changed) { - var setting = realm.All().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString()); + var setting = r.All().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString()); setting.Value = ConfigStore[c].ToString(); } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 606bc65599..9af9ace7ad 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets { public class RulesetStore : IDisposable, IRulesetStore { - private readonly RealmAccess realm; + private readonly RealmAccess realmAccess; private const string ruleset_library_prefix = @"osu.Game.Rulesets"; @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets public RulesetStore(RealmAccess realm, Storage? storage = null) { - this.realm = realm; + realmAccess = realm; // On android in release configuration assemblies are loaded from the apk directly into memory. // We cannot read assemblies from cwd, so should check loaded assemblies instead. @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets private void addMissingRulesets() { - realm.Write(realm => + realmAccess.Write(realm => { var rulesets = realm.All(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b02abd2f7a..dff2c598c3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -191,12 +191,12 @@ namespace osu.Game.Screens.Select base.LoadComplete(); subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); - subscriptionBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => !b.Hidden), beatmapsChanged); + subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => !b.Hidden), beatmapsChanged); // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realm.RegisterForNotifications(realm => realm.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); - subscriptionHiddenBeatmaps = realm.RegisterForNotifications(realm => realm.All().Where(b => b.Hidden), beatmapsChanged); + subscriptionDeletedSets = realm.RegisterForNotifications(r => r.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); + subscriptionHiddenBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => b.Hidden), beatmapsChanged); } private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 021dfd06f7..e1f9c1b508 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -48,13 +48,13 @@ namespace osu.Game.Screens.Select.Carousel ruleset.BindValueChanged(_ => { scoreSubscription?.Dispose(); - scoreSubscription = realm.RegisterForNotifications(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" - + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" - + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" - + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) - .OrderByDescending(s => s.TotalScore), + scoreSubscription = realm.RegisterForNotifications(r => + r.All() + .Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0" + + $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $1" + + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) + .OrderByDescending(s => s.TotalScore), (items, changes, ___) => { Rank = items.FirstOrDefault()?.Rank; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index b53aa6f43c..f25997650b 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -113,9 +113,9 @@ namespace osu.Game.Screens.Select.Leaderboards if (beatmapInfo == null) return; - scoreSubscription = realm.RegisterForNotifications(realm => - realm.All() - .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), + scoreSubscription = realm.RegisterForNotifications(r => + r.All() + .Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} = $0", beatmapInfo.ID), (_, changes, ___) => { if (!IsOnlineScope) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 22fb8ce86f..344acb7a0f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -87,12 +87,12 @@ namespace osu.Game.Skinning }; // Ensure the default entries are present. - realm.Write(realm => + realm.Write(r => { foreach (var skin in defaultSkins) { - if (realm.Find(skin.SkinInfo.ID) == null) - realm.Add(skin.SkinInfo.Value); + if (r.Find(skin.SkinInfo.ID) == null) + r.Add(skin.SkinInfo.Value); } }); diff --git a/osu.Game/Stores/RealmFileStore.cs b/osu.Game/Stores/RealmFileStore.cs index 5edc1be954..b5dd3d64e4 100644 --- a/osu.Game/Stores/RealmFileStore.cs +++ b/osu.Game/Stores/RealmFileStore.cs @@ -92,10 +92,10 @@ namespace osu.Game.Stores int removedFiles = 0; // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. - realm.Write(realm => + realm.Write(r => { // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) - var files = realm.All().ToList(); + var files = r.All().ToList(); foreach (var file in files) { @@ -108,7 +108,7 @@ namespace osu.Game.Stores { removedFiles++; Storage.Delete(file.GetStoragePath()); - realm.Remove(file); + r.Remove(file); } catch (Exception e) { From bbcc149e2e7cc01045374b7b25be85983658aa0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:42:41 +0900 Subject: [PATCH 68/85] During import if files are found to be missing, ensure they are restored This is one step closer to sanity in terms of physical files. As per the comment I have left in place, we really should be checking file sizes or hashes, but to keep things simple and fast I've opted to just cover the "missing file" scenario for now. Ran into this when testing against a foreign `client.realm` by: - Noticing a beatmap doesn't load - Deleting said beatmap - Downloading via beatmap overlay - Beatmap is restored but still doesn't work Note that I've kept the logic where this will undelete an existing import rather than create one from fresh, as I think that is beneficial to the user (ie. it will still keep any linked scores on restore). --- .../Database/BeatmapImporterTests.cs | 32 +++++++++++++++++++ osu.Game/Stores/RealmArchiveModelImporter.cs | 10 ++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 227314cffd..abab8ae3c6 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -600,6 +600,38 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestImportThenReimportAfterMissingFiles() + { + RunTestWithRealmAsync(async (realmFactory, storage) => + { + using var importer = new BeatmapModelManager(realmFactory, storage); + using var store = new RulesetStore(realmFactory, storage); + + var imported = await LoadOszIntoStore(importer, realmFactory.Context); + + deleteBeatmapSet(imported, realmFactory.Context); + + Assert.IsTrue(imported.DeletePending); + + // 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.Context); + + // 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() { diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index 3d8e9f2703..154135ea71 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -342,7 +342,8 @@ namespace osu.Game.Stores // note that this should really be checking filesizes on disk (of existing files) for some degree of sanity. // or alternatively doing a faster hash check. either of these require database changes and reprocessing of existing files. if (CanSkipImport(existing, item) && - getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f))) + getFilenames(existing.Files).SequenceEqual(getShortenedFilenames(archive).Select(p => p.shortened).OrderBy(f => f)) && + checkAllFilesExist(existing)) { LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -459,7 +460,6 @@ namespace osu.Game.Stores if (!(prefix.EndsWith('/') || prefix.EndsWith('\\'))) prefix = string.Empty; - // import files to manager foreach (string file in reader.Filenames) yield return (file, file.Substring(prefix.Length).ToStandardisedPath()); } @@ -519,7 +519,11 @@ namespace osu.Game.Stores // for the best or worst, we copy and import files of a new import before checking whether // it is a duplicate. so to check if anything has changed, we can just compare all File IDs. getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && - getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)); + getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)) && + checkAllFilesExist(existing); + + private bool checkAllFilesExist(TModel model) => + model.Files.All(f => Files.Storage.Exists(f.File.GetStoragePath())); /// /// Whether this specified path should be removed after successful import. From df1297ade6fcdf95b9025252aae63cbebcd0ba0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:50:39 +0900 Subject: [PATCH 69/85] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index b296c114e9..4e5b9fdbb1 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 758575e74a..af5d8a5920 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 5925581e28..2bcdea61b3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From dd2caea694f0e2782c4c34b8dba3c165ef9136da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 19:10:14 +0900 Subject: [PATCH 70/85] Update `GetSuitableHost` usages in line with new `HostOptions` --- osu.Desktop/Program.cs | 2 +- osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs | 3 ++- .../NonVisual/CustomTourneyDirectoryTest.cs | 5 +++-- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 3 ++- osu.Game.Tournament.Tests/TournamentTestRunner.cs | 2 +- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 6 +++++- osu.Game/Tests/VisualTestRunner.cs | 2 +- 7 files changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 7ec7d53a7e..ab9935a220 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -55,7 +55,7 @@ namespace osu.Desktop } } - using (DesktopGameHost host = Host.GetSuitableHost(gameName, true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { PortableInstallation = true })) { host.ExceptionThrown += handleException; diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 53e4ef07e7..ee746ba4d5 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -6,6 +6,7 @@ using System.IO; using System.Text; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; @@ -155,7 +156,7 @@ namespace osu.Game.Tests.Collections.IO } // Name matches the automatically chosen name from `CleanRunHeadlessGameHost` above, so we end up using the same storage location. - using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName)) + using (HeadlessGameHost host = new TestRunHeadlessGameHost(firstRunName, null)) { try { diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index fc5d3b652f..36d0c6f0f6 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; @@ -37,7 +38,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 +69,7 @@ namespace osu.Game.Tournament.Tests.NonVisual [Test] public void TestMigration() { - using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestMigration))) // don't use clean run as we are writing test files for migration. + using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestMigration), null)) // don't use clean run as we are writing test files for migration. { string osuRoot = Path.Combine(host.UserStoragePaths.First(), nameof(TestMigration)); string configFile = Path.Combine(osuRoot, "tournament.ini"); diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 03252e3be6..11b686e3e8 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; @@ -19,7 +20,7 @@ namespace osu.Game.Tournament.Tests.NonVisual public void CheckIPCLocation() { // don't use clean run because files are being written before osu! launches. - using (var host = new TestRunHeadlessGameHost(nameof(CheckIPCLocation))) + using (var host = new TestRunHeadlessGameHost(nameof(CheckIPCLocation), null)) { string basePath = Path.Combine(host.UserStoragePaths.First(), nameof(CheckIPCLocation)); diff --git a/osu.Game.Tournament.Tests/TournamentTestRunner.cs b/osu.Game.Tournament.Tests/TournamentTestRunner.cs index 1f63f7c545..23da37a6b7 100644 --- a/osu.Game.Tournament.Tests/TournamentTestRunner.cs +++ b/osu.Game.Tournament.Tests/TournamentTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tournament.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true })) { host.Run(new TournamentTestBrowser()); return 0; diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index 754c9044e8..bdb171c528 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using osu.Framework; using osu.Framework.Testing; namespace osu.Game.Tests @@ -20,7 +21,10 @@ namespace osu.Game.Tests /// Whether to bypass directory cleanup on host disposal. Should be used only if a subsequent test relies on the files still existing. /// The name of the calling method, used for test file isolation and clean-up. public CleanRunHeadlessGameHost(bool bindIPC = false, bool realtime = true, bool bypassCleanup = false, [CallerMemberName] string callingMethodName = @"") - : base($"{callingMethodName}-{Guid.NewGuid()}", bindIPC, realtime, bypassCleanup: bypassCleanup) + : base($"{callingMethodName}-{Guid.NewGuid()}", new HostOptions + { + BindIPC = bindIPC, + }, bypassCleanup: bypassCleanup, realtime: realtime) { } diff --git a/osu.Game/Tests/VisualTestRunner.cs b/osu.Game/Tests/VisualTestRunner.cs index d63b3d48b2..973801099e 100644 --- a/osu.Game/Tests/VisualTestRunner.cs +++ b/osu.Game/Tests/VisualTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true, })) { host.Run(new OsuTestBrowser()); return 0; From a5c76a9647c56b1677bd9105c93220ee7ad59441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 13:56:47 +0900 Subject: [PATCH 71/85] Fix a few more cases of "context" terminology usage --- osu.Game/Database/RealmAccess.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 66b4edbe84..65e13cf542 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -30,7 +30,7 @@ using Realms.Exceptions; namespace osu.Game.Database { /// - /// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage. + /// A factory which provides safe access to the realm storage backend. /// public class RealmAccess : IDisposable { @@ -57,9 +57,9 @@ namespace osu.Game.Database private const int schema_version = 13; /// - /// Lock object which is held during sections, blocking context creation during blocking periods. + /// Lock object which is held during sections, blocking realm retrieval during blocking periods. /// - private readonly SemaphoreSlim realmCreationLock = new SemaphoreSlim(1); + private readonly SemaphoreSlim realmRetrievalLock = new SemaphoreSlim(1); private readonly ThreadLocal currentThreadCanCreateRealmInstances = new ThreadLocal(); @@ -76,7 +76,7 @@ namespace osu.Game.Database /// /// Holds a map of functions registered via and a coinciding action which when triggered, - /// fires a change set event with an empty collection. This is used to inform subscribers when a realm context goes away, and ensure they don't use invalidated + /// fires a change set event with an empty collection. This is used to inform subscribers when the main realm instance gets recycled, and ensure they don't use invalidated /// managed realm objects from a previous firing. /// private readonly Dictionary, Action> notificationsResetMap = new Dictionary, Action>(); @@ -364,7 +364,7 @@ namespace osu.Game.Database { if (!currentThreadCanCreateRealmInstances.Value) { - realmCreationLock.Wait(); + realmRetrievalLock.Wait(); currentThreadCanCreateRealmInstances.Value = true; tookSemaphoreLock = true; } @@ -383,7 +383,7 @@ namespace osu.Game.Database { if (tookSemaphoreLock) { - realmCreationLock.Release(); + realmRetrievalLock.Release(); currentThreadCanCreateRealmInstances.Value = false; } } @@ -589,7 +589,7 @@ namespace osu.Game.Database try { - realmCreationLock.Wait(); + realmRetrievalLock.Wait(); lock (realmLock) { @@ -639,13 +639,13 @@ namespace osu.Game.Database } catch { - realmCreationLock.Release(); + realmRetrievalLock.Release(); throw; } return new InvokeOnDisposal(this, factory => { - factory.realmCreationLock.Release(); + factory.realmRetrievalLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); // Post back to the update thread to revive any subscriptions. @@ -667,9 +667,9 @@ namespace osu.Game.Database if (!isDisposed) { - // intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal. - realmCreationLock.Wait(); - realmCreationLock.Dispose(); + // intentionally block realm retrieval indefinitely. this ensures that nothing can start consuming a new instance after disposal. + realmRetrievalLock.Wait(); + realmRetrievalLock.Dispose(); isDisposed = true; } From 8116806db3fa6b06e96d1f38eac77964a96435a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:00:58 +0900 Subject: [PATCH 72/85] Add test coverage of calling `BlockAllOperations` a second time after timeout --- osu.Game.Tests/Database/GeneralUsageTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 2533c832e6..8262ef18d4 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -95,6 +95,11 @@ namespace osu.Game.Tests.Database }); stopThreadedUsage.Set(); + + // Ensure we can block a second time after the usage has ended. + using (realm.BlockAllOperations()) + { + } }); } } From 86c844bd58317c2fc5d978a6142ea3ac153794a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:15:29 +0900 Subject: [PATCH 73/85] Update remaining usages of `GetSuitableHost` in template projects --- .../osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs | 2 +- .../osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs | 2 +- osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs | 1 - .../NonVisual/CustomTourneyDirectoryTest.cs | 1 - osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 1 - 7 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs index 4f810ce17f..03ee7c9204 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs index fd6bd9b714..55c0cf6a3b 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs index 65cfb2bff4..b45505678c 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs index fd6bd9b714..55c0cf6a3b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new OsuTestBrowser()); return 0; diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index ee746ba4d5..5cbede54f5 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -6,7 +6,6 @@ using System.IO; using System.Text; using System.Threading.Tasks; using NUnit.Framework; -using osu.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 36d0c6f0f6..26fb03bed4 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using NUnit.Framework; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 11b686e3e8..80cc9be5c1 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using NUnit.Framework; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; From 5872dabf604f55a1feec63039404780a9d48133b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:16:15 +0900 Subject: [PATCH 74/85] Fix incorrect flag to options conversion --- osu.Desktop/Program.cs | 2 +- osu.Game.Tournament.Tests/TournamentTestRunner.cs | 2 +- osu.Game/Tests/VisualTestRunner.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index ab9935a220..b944068e78 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -55,7 +55,7 @@ namespace osu.Desktop } } - using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { PortableInstallation = true })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = true })) { host.ExceptionThrown += handleException; diff --git a/osu.Game.Tournament.Tests/TournamentTestRunner.cs b/osu.Game.Tournament.Tests/TournamentTestRunner.cs index 23da37a6b7..229ab41a1e 100644 --- a/osu.Game.Tournament.Tests/TournamentTestRunner.cs +++ b/osu.Game.Tournament.Tests/TournamentTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tournament.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true })) { host.Run(new TournamentTestBrowser()); return 0; diff --git a/osu.Game/Tests/VisualTestRunner.cs b/osu.Game/Tests/VisualTestRunner.cs index 973801099e..6aa75ec147 100644 --- a/osu.Game/Tests/VisualTestRunner.cs +++ b/osu.Game/Tests/VisualTestRunner.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests [STAThread] public static int Main(string[] args) { - using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { PortableInstallation = true, })) + using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true, })) { host.Run(new OsuTestBrowser()); return 0; From 56d7d814658d1d202e405f237cf74d8a386d078b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 14:47:21 +0900 Subject: [PATCH 75/85] Fix broken test due to `SynchronizationContext` never running expected work --- osu.Game.Tests/Database/GeneralUsageTests.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 8262ef18d4..dc0d42595b 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -87,6 +87,11 @@ namespace osu.Game.Tests.Database hasThreadedUsage.Wait(); + // Usually the host would run the synchronization context work per frame. + // For the sake of keeping this test simple (there's only one update invocation), + // let's replace it so we can ensure work is run immediately. + SynchronizationContext.SetSynchronizationContext(new ImmediateExecuteSynchronizationContext()); + Assert.Throws(() => { using (realm.BlockAllOperations()) @@ -102,5 +107,10 @@ namespace osu.Game.Tests.Database } }); } + + private class ImmediateExecuteSynchronizationContext : SynchronizationContext + { + public override void Post(SendOrPostCallback d, object? state) => d(state); + } } } From 5fb9b58c9b12c2929d07eaccfabf71528f259fe6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 24 Jan 2022 20:23:10 +0900 Subject: [PATCH 76/85] Add tracking of total subscriptions --- osu.Game/Database/RealmAccess.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 65e13cf542..fe4c30d7dd 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -83,6 +83,8 @@ namespace osu.Game.Database private static readonly GlobalStatistic realm_instances_created = GlobalStatistics.Get(@"Realm", @"Instances (Created)"); + private static readonly GlobalStatistic total_subscriptions = GlobalStatistics.Get(@"Realm", @"Subscriptions"); + private readonly object realmLock = new object(); private Realm? updateRealm; @@ -289,6 +291,8 @@ namespace osu.Game.Database var syncContext = SynchronizationContext.Current; + total_subscriptions.Value++; + registerSubscription(action); // This token is returned to the consumer. @@ -309,6 +313,7 @@ namespace osu.Game.Database unsubscriptionAction?.Dispose(); customSubscriptionsResetMap.Remove(action); notificationsResetMap.Remove(action); + total_subscriptions.Value--; } } } From ae0fea8e2627f68f211c89a27a9bae04a651b0f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 15:29:45 +0900 Subject: [PATCH 77/85] Fix compilation issues due to misnamed fild --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 8cb762ed12..b37d2be0bc 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -608,9 +608,9 @@ namespace osu.Game.Tests.Database 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); @@ -619,7 +619,7 @@ namespace osu.Game.Tests.Database Assert.That(imported.Files.All(f => !storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath()))); - var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); + 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); From 778d2a71b484bb3220a09db15fc5fc632cb891e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 15:23:51 +0900 Subject: [PATCH 78/85] Remove `Task` from the inner-most `Import` method in `RealmArchiveModelImporter` One of my pending work items for post-realm merge. The lowest-level import task is no longer asynchronous, as we don't want it to span multiple threads to allow easier interaction with realm. Removing the `Task` spec simplifies a heap of usages. Individual usages should decide whether they want to run the import asynchronously, by either using an alternative override or spooling up a thread themselves. --- .../Database/BeatmapImporterTests.cs | 4 ++-- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 10 ++++----- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 22 +++++++++---------- .../Gameplay/TestSceneReplayDownloadButton.cs | 2 +- .../Menus/TestSceneMusicActionHandling.cs | 2 +- .../TestSceneDrawableRoomPlaylist.cs | 3 +-- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestScenePlaylistsSongSelect.cs | 3 +-- .../Navigation/TestScenePresentBeatmap.cs | 3 +-- .../Navigation/TestScenePresentScore.cs | 5 ++--- .../TestScenePlaylistsRoomCreation.cs | 7 +++--- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../TestSceneBeatmapRecommendations.cs | 3 +-- .../SongSelect/TestScenePlaySongSelect.cs | 13 +++++------ .../TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 5 ++--- osu.Game/Database/IModelImporter.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Screens/Play/Player.cs | 8 ++++--- osu.Game/Skinning/SkinManager.cs | 5 ++--- osu.Game/Stores/RealmArchiveModelImporter.cs | 14 +++++++----- 21 files changed, 57 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index b37d2be0bc..69dd2d930a 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -685,7 +685,7 @@ namespace osu.Game.Tests.Database [Test] public void TestImportWithDuplicateBeatmapIDs() { - RunTestWithRealmAsync(async (realm, storage) => + RunTestWithRealm((realm, storage) => { using var importer = new BeatmapModelManager(realm, storage); using var store = new RulesetStore(realm, storage); @@ -718,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); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 144cbe15c3..95c15367aa 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -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,7 +164,7 @@ namespace osu.Game.Tests.Online { public TaskCompletionSource AllowImport = new TaskCompletionSource(); - public Task> CurrentImportTask { get; private set; } + public ILive CurrentImport { get; private set; } public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) @@ -186,10 +186,10 @@ namespace osu.Game.Tests.Online this.testBeatmapManager = testBeatmapManager; } - public override async Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public override ILive Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - await testBeatmapManager.AllowImport.Task.ConfigureAwait(false); - return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false); + testBeatmapManager.AllowImport.Task.WaitSafely(); + return (testBeatmapManager.CurrentImport = base.Import(item, archive, lowPriority, cancellationToken)); } } } diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index dd12c94855..8de9f0a292 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -25,7 +24,7 @@ namespace osu.Game.Tests.Scores.IO public class ImportScoreTest : ImportTest { [Test] - public async Task TestBasicImport() + public void TestBasicImport() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -49,7 +48,7 @@ namespace osu.Game.Tests.Scores.IO BeatmapInfo = beatmap.Beatmaps.First() }; - var imported = await LoadScoreIntoOsu(osu, toImport); + var imported = LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Rank, imported.Rank); Assert.AreEqual(toImport.TotalScore, imported.TotalScore); @@ -67,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public async Task TestImportMods() + public void TestImportMods() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -85,7 +84,7 @@ namespace osu.Game.Tests.Scores.IO Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; - var imported = await LoadScoreIntoOsu(osu, toImport); + var imported = LoadScoreIntoOsu(osu, toImport); Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); @@ -98,7 +97,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public async Task TestImportStatistics() + public void TestImportStatistics() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -120,7 +119,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await LoadScoreIntoOsu(osu, toImport); + var imported = LoadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); @@ -133,7 +132,7 @@ namespace osu.Game.Tests.Scores.IO } [Test] - public async Task TestOnlineScoreIsAvailableLocally() + public void TestOnlineScoreIsAvailableLocally() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { @@ -143,7 +142,7 @@ namespace osu.Game.Tests.Scores.IO var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely(); - await LoadScoreIntoOsu(osu, new ScoreInfo + LoadScoreIntoOsu(osu, new ScoreInfo { User = new APIUser { Username = "Test user" }, BeatmapInfo = beatmap.Beatmaps.First(), @@ -168,13 +167,14 @@ namespace osu.Game.Tests.Scores.IO } } - public static async Task LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) + public static ScoreInfo LoadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { // clone to avoid attaching the input score to realm. score = score.DeepClone(); var scoreManager = osu.Dependencies.Get(); - await scoreManager.Import(score, archive); + + scoreManager.Import(score, archive); return scoreManager.Query(_ => true); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 8199389b36..a12171401a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); - AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).GetResultSafely()); + AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true))); AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 3ebc64cd0b..10a82089b3 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Menus Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. - AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).WaitSafely(), 5); + AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5); AddStep("import beatmap with track", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 30ac1302aa..92accb0cd1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -8,7 +8,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -158,7 +157,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(beatmap.BeatmapSet != null); - AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet).GetResultSafely()); + AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet)); createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 7e3c9722cf..5465061891 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmapSetInfo.Beatmaps.Add(beatmap); } - manager.Import(beatmapSetInfo).WaitSafely(); + manager.Import(beatmapSetInfo); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index d7a0885c95..d933491ab6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -6,7 +6,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Utils; @@ -40,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); - manager.Import(beatmapSet).WaitSafely(); + manager.Import(beatmapSet); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index f6c53e76c4..63226de750 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -125,7 +124,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, } - }).GetResultSafely()?.Value; + })?.Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 7bd8110374..7656bf79dc 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Extensions; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -60,7 +59,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = new OsuRuleset().RulesetInfo }, } - }).GetResultSafely()?.Value; + })?.Value; }); } @@ -135,7 +134,7 @@ namespace osu.Game.Tests.Visual.Navigation BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo, User = new GuestUser(), - }).GetResultSafely().Value; + }).Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index d397b37d05..68225f6d64 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -8,7 +8,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -151,7 +150,7 @@ namespace osu.Game.Tests.Visual.Playlists Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null); - manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); + manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet); }); // Create the room using the real beatmap values. @@ -196,7 +195,7 @@ namespace osu.Game.Tests.Visual.Playlists Debug.Assert(originalBeatmap.BeatmapInfo.BeatmapSet != null); - manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).WaitSafely(); + manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet); }); AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash); @@ -219,7 +218,7 @@ namespace osu.Game.Tests.Visual.Playlists Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); - importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet).GetResultSafely()?.Value.Detach(); + importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)?.Value.Detach(); }); private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 5e977d075d..e31be1d51a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -180,7 +180,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"Load new scores via manager", () => { foreach (var score in generateSampleScores(beatmapInfo())) - scoreManager.Import(score).WaitSafely(); + scoreManager.Import(score); }); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index b7bc0c37e1..940d001c5b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Extensions; @@ -184,7 +183,7 @@ namespace osu.Game.Tests.Visual.SongSelect beatmap.DifficultyName = $"SR{i + 1}"; } - return Game.BeatmapManager.Import(beatmapSet).GetResultSafely()?.Value; + return Game.BeatmapManager.Import(beatmapSet)?.Value; } private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 0dc4556ed9..630171a4d0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -8,7 +8,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -260,7 +259,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)); }); } else @@ -675,7 +674,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)); }); int previousSetID = 0; @@ -715,7 +714,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import multi-ruleset map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)); }); DrawableCarouselBeatmapSet set = null; @@ -764,7 +763,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import huge difficulty count map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); - imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).GetResultSafely()?.Value; + imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets))?.Value; }); AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); @@ -873,7 +872,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); - private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())).WaitSafely(); + private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); @@ -903,7 +902,7 @@ namespace osu.Game.Tests.Visual.SongSelect var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); for (int i = 0; i < 10; i++) - manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)).WaitSafely(); + manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)); }); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 55031a9699..4826d2fb33 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -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); } }); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4488301fe5..a1c1982f00 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Audio; using osu.Framework.Audio.Track; -using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Testing; @@ -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"); @@ -295,7 +294,7 @@ namespace osu.Game.Beatmaps return beatmapModelManager.Import(archive, lowPriority, cancellationToken); } - public Task?> Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public ILive? Import(BeatmapSetInfo item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index d00cfb2035..3047a1d30a 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -45,7 +45,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); + ILive? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// A user displayable name for the model type associated with this manager. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index bb26bd3b04..3a842a048a 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -308,7 +308,7 @@ namespace osu.Game.Scoring return scoreModelManager.Import(archive, lowPriority, cancellationToken); } - public Task> Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public ILive Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4d3201cd27..cfca2d0a3d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1024,11 +1024,11 @@ namespace osu.Game.Screens.Play /// /// The to import. /// The imported score. - protected virtual async Task ImportScore(Score score) + protected virtual Task ImportScore(Score score) { // Replays are already populated and present in the game's database, so should not be re-imported. if (DrawableRuleset.ReplayScore != null) - return; + return Task.CompletedTask; LegacyByteArrayReader replayReader; @@ -1048,7 +1048,7 @@ namespace osu.Game.Screens.Play // conflicts across various systems (ie. solo and multiplayer). importableScore.OnlineID = -1; - var imported = await scoreManager.Import(importableScore, replayReader).ConfigureAwait(false); + var imported = scoreManager.Import(importableScore, replayReader); imported.PerformRead(s => { @@ -1056,6 +1056,8 @@ namespace osu.Game.Screens.Play score.ScoreInfo.Hash = s.Hash; score.ScoreInfo.ID = s.ID; }); + + return Task.CompletedTask; } /// diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 344acb7a0f..47c7bc060a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -11,7 +11,6 @@ using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; @@ -151,7 +150,7 @@ namespace osu.Game.Skinning Name = s.Name + @" (modified)", Creator = s.Creator, InstantiationInfo = s.InstantiationInfo, - }).GetResultSafely(); + }); if (result != null) { @@ -278,7 +277,7 @@ namespace osu.Game.Skinning return skinModelManager.Import(archive, lowPriority, cancellationToken); } - public Task> Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public ILive Import(SkinInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return skinModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Stores/RealmArchiveModelImporter.cs b/osu.Game/Stores/RealmArchiveModelImporter.cs index b0f676ecf0..43c1c7c888 100644 --- a/osu.Game/Stores/RealmArchiveModelImporter.cs +++ b/osu.Game/Stores/RealmArchiveModelImporter.cs @@ -250,8 +250,10 @@ namespace osu.Game.Stores return null; } - var scheduledImport = Task.Factory.StartNew(async () => await Import(model, archive, lowPriority, cancellationToken).ConfigureAwait(false), - cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap(); + var scheduledImport = Task.Factory.StartNew(() => Import(model, archive, lowPriority, cancellationToken), + cancellationToken, + TaskCreationOptions.HideScheduler, + lowPriority ? import_scheduler_low_priority : import_scheduler); return await scheduledImport.ConfigureAwait(false); } @@ -318,7 +320,7 @@ namespace osu.Game.Stores /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - public virtual Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public virtual ILive? Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return Realm.Run(realm => { @@ -353,7 +355,7 @@ namespace osu.Game.Stores transaction.Commit(); } - return Task.FromResult((ILive?)existing.ToLive(Realm)); + return existing.ToLive(Realm); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -388,7 +390,7 @@ namespace osu.Game.Stores existing.DeletePending = false; transaction.Commit(); - return Task.FromResult((ILive?)existing.ToLive(Realm)); + return existing.ToLive(Realm); } LogForModel(item, @"Found existing but failed re-use check."); @@ -414,7 +416,7 @@ namespace osu.Game.Stores throw; } - return Task.FromResult((ILive?)item.ToLive(Realm)); + return (ILive?)item.ToLive(Realm); }); } From fc58b202b1d93b98d4d90b22741a5502591d5c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 15:45:23 +0900 Subject: [PATCH 79/85] Fix crash when trying to migrate collection database that doesn't exist --- osu.Game/OsuGameBase.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7a6a126900..09ca4e450d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -208,8 +208,13 @@ namespace osu.Game 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); From 1f9cf00db89f7a80027e665d86a3cfd8e1891b31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 16:44:44 +0900 Subject: [PATCH 80/85] Fix `DatabasedKeyBindingContainer` re-querying realm on receiving notification --- .../Bindings/DatabasedKeyBindingContainer.cs | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 3e4a9759a3..ba129b93e5 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Database; using osu.Game.Rulesets; +using Realms; namespace osu.Game.Input.Bindings { @@ -46,41 +47,36 @@ namespace osu.Game.Input.Bindings throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided."); } - private IQueryable queryRealmKeyBindings() - { - string rulesetName = ruleset?.ShortName; - return realm.Realm.All() - .Where(b => b.RulesetName == rulesetName && b.Variant == variant); - } - protected override void LoadComplete() { - realmSubscription = realm.RegisterForNotifications(r => queryRealmKeyBindings(), (sender, changes, error) => + 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(); + reloadMappings(sender.AsQueryable()); }); base.LoadComplete(); } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); + protected override void ReloadMappings() => reloadMappings(queryRealmKeyBindings(realm.Realm)); - realmSubscription?.Dispose(); + private IQueryable queryRealmKeyBindings(Realm realm) + { + string rulesetName = ruleset?.ShortName; + return realm.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant); } - protected override void ReloadMappings() + private void reloadMappings(IQueryable realmKeyBindings) { var defaults = DefaultKeyBindings.ToList(); - List newBindings = queryRealmKeyBindings().Detach() - // this ordering is important to ensure that we read entries from the database in the order - // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise - // have been eaten by the music controller due to query order. - .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); + List newBindings = realmKeyBindings.Detach() + // this ordering is important to ensure that we read entries from the database in the order + // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise + // have been eaten by the music controller due to query order. + .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.ActionInt)).ToList(); // In the case no bindings were found in the database, presume this usage is for a non-databased ruleset. // This actually should never be required and can be removed if it is ever deemed to cause a problem. @@ -91,5 +87,12 @@ namespace osu.Game.Input.Bindings else KeyBindings = newBindings; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + realmSubscription?.Dispose(); + } } } From b2b6672095d10a8da9a005dd87d072212893e454 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 11:43:05 +0300 Subject: [PATCH 81/85] Add failing test asserts --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index bf3b46c6f7..96f815621c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; @@ -40,6 +41,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddStep("Set beat divisor", () => editor.Dependencies.Get().Value = 16); AddStep("Set overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty = 7); AddStep("Set artist and title", () => { @@ -88,6 +90,7 @@ namespace osu.Game.Tests.Visual.Editing private void checkMutations() { AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); + AddAssert("Beatmap has correct beat divisor", () => editorBeatmap.BeatmapInfo.BeatDivisor == 16); AddAssert("Beatmap has correct overall difficulty", () => editorBeatmap.Difficulty.OverallDifficulty == 7); AddAssert("Beatmap has correct metadata", () => editorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && editorBeatmap.BeatmapInfo.Metadata.Title == "title"); AddAssert("Beatmap has correct author", () => editorBeatmap.BeatmapInfo.Metadata.Author.Username == "author"); From f7f58b06a1bd107100d3ed0ee23c65bb0925112c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 11:43:16 +0300 Subject: [PATCH 82/85] Fix beat divisor not saving in editor --- osu.Game/Screens/Edit/Editor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8c4b458534..b42f629aad 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -158,9 +158,6 @@ namespace osu.Game.Screens.Edit return; } - beatDivisor.Value = playableBeatmap.BeatmapInfo.BeatDivisor; - beatDivisor.BindValueChanged(divisor => playableBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue); - // Todo: should probably be done at a DrawableRuleset level to share logic with Player. clock = new EditorClock(playableBeatmap, beatDivisor) { IsCoupled = false }; clock.ChangeSource(loadableBeatmap.Track); @@ -178,6 +175,9 @@ namespace osu.Game.Screens.Edit changeHandler = new EditorChangeHandler(editorBeatmap); dependencies.CacheAs(changeHandler); + beatDivisor.Value = editorBeatmap.BeatmapInfo.BeatDivisor; + beatDivisor.BindValueChanged(divisor => editorBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue); + updateLastSavedHash(); Schedule(() => From a93873e8ca0f9290c5be236b363337d71042824f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 13:01:30 +0300 Subject: [PATCH 83/85] Recreate test beatmap of `EditorTestScene` on set up --- osu.Game/Tests/Visual/EditorTestScene.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 6f751b1736..f7d62a8694 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -43,28 +43,16 @@ namespace osu.Game.Tests.Visual }; private TestBeatmapManager testBeatmapManager; - private WorkingBeatmap working; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio, RulesetStore rulesets) { Add(logo); - working = CreateWorkingBeatmap(Ruleset.Value); - if (IsolateSavingFromDatabase) Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); } - protected override void LoadComplete() - { - base.LoadComplete(); - - Beatmap.Value = working; - if (testBeatmapManager != null) - testBeatmapManager.TestBeatmap = working; - } - protected virtual bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; @@ -78,6 +66,11 @@ namespace osu.Game.Tests.Visual protected virtual void LoadEditor() { + Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); + + if (testBeatmapManager != null) + testBeatmapManager.TestBeatmap = Beatmap.Value; + LoadScreen(editorLoader = new TestEditorLoader()); } From 6c69df815a83f718d59354744321ce99ca5b644a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jan 2022 15:25:28 +0300 Subject: [PATCH 84/85] Update editor test scenes to set working beatmap properly --- .../Visual/Editing/TestSceneDifficultySwitching.cs | 8 +++----- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 7 ++----- .../Visual/Editing/TestSceneEditorTestGameplay.cs | 5 ++++- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 243bb71e26..cf6488f721 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps.IO; namespace osu.Game.Tests.Visual.Editing @@ -37,11 +38,8 @@ namespace osu.Game.Tests.Visual.Editing base.SetUpSteps(); } - protected override void LoadEditor() - { - Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First()); - base.LoadEditor(); - } + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First()); [Test] public void TestBasicSwitch() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 2386446e96..e3fb44534b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; +using osu.Game.Storyboards; using osu.Game.Tests.Resources; using SharpCompress.Archives; using SharpCompress.Archives.Zip; @@ -39,11 +40,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); } - protected override void LoadEditor() - { - Beatmap.Value = new DummyWorkingBeatmap(Audio, null); - base.LoadEditor(); - } + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null); [Test] public void TestCreateNewBeatmap() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index bb630e5d5c..79afc8cf27 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -17,6 +17,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Play; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps.IO; using osuTK.Graphics; using osuTK.Input; @@ -43,9 +44,11 @@ namespace osu.Game.Tests.Visual.Editing base.SetUpSteps(); } + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); + protected override void LoadEditor() { - Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); SelectedMods.Value = new[] { new ModCinema() }; base.LoadEditor(); } From cdef67ccd0840053192a151b9d75f303e44ecb07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jan 2022 23:38:46 +0900 Subject: [PATCH 85/85] Log posted notifications To help with test failures and the likes. --- osu.Game/Overlays/NotificationOverlay.cs | 3 +++ osu.Game/Overlays/Notifications/Notification.cs | 3 +++ osu.Game/Overlays/Notifications/ProgressNotification.cs | 2 +- osu.Game/Overlays/Notifications/SimpleNotification.cs | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 8809dec642..e4e3931048 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -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) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 44203e8ee7..ec6e9e09b3 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -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 /// public event Action Closed; + public abstract LocalisableString Text { get; set; } + /// /// Whether this notification should forcefully display itself. /// diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 5b74bff817..4735fcb7c1 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Notifications private LocalisableString text; - public LocalisableString Text + public override LocalisableString Text { get => text; set diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index c32e40ffc8..b9a1cc6d90 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Notifications { private LocalisableString text; - public LocalisableString Text + public override LocalisableString Text { get => text; set