From cd9bf0c753c0d4e6ca86c4ccc17fe1a668654e8e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 3 Jan 2024 21:30:46 -0800 Subject: [PATCH 01/35] Flash blocking ongoing operations dialog when trying to force quit --- osu.Game/Screens/Menu/MainMenu.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 516b090a16..14c950d726 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -25,6 +26,7 @@ using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; @@ -390,7 +392,12 @@ namespace osu.Game.Screens.Menu if (requiresConfirmation) { if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog) - exitDialog.PerformOkAction(); + { + if (exitDialog.Buttons.OfType().FirstOrDefault() != null) + exitDialog.PerformOkAction(); + else + exitDialog.Flash(); + } else { dialogOverlay.Push(new ConfirmExitDialog(() => From ea714c86d427cb6fa498b1d76a02e6ee389dae6f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 3 Jan 2024 22:22:25 -0800 Subject: [PATCH 02/35] Fix potential null reference with flash sample when exiting rapidly Fixes `TestForceExitWithOperationInProgress()`. --- osu.Game/Overlays/Dialog/PopupDialog.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 4048b35e78..4ac37a63e2 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Dialog private readonly Vector2 ringMinifiedSize = new Vector2(20f); private readonly Box flashLayer; - private Sample flashSample = null!; + private Sample? flashSample; private readonly Container content; private readonly Container ring; @@ -267,7 +267,7 @@ namespace osu.Game.Overlays.Dialog flashLayer.FadeInFromZero(80, Easing.OutQuint) .Then() .FadeOutFromOne(1500, Easing.OutQuint); - flashSample.Play(); + flashSample?.Play(); } protected override bool OnKeyDown(KeyDownEvent e) From 1beb3f5462329262b902472227a3b6c097714092 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 15:45:13 +0900 Subject: [PATCH 03/35] Add failing test coverage of fast streams not working in editor --- .../TestSceneEditorAutoplayFastStreams.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs new file mode 100644 index 0000000000..cf5cd809ef --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs @@ -0,0 +1,51 @@ +// 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.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + /// + /// This test covers autoplay working correctly in the editor on fast streams. + /// Might seem like a weird test, but frame stability being toggled can cause autoplay to operation incorrectly. + /// This is clearly a bug with the autoplay algorithm, but is worked around at an editor level for now. + /// + public partial class TestSceneEditorAutoplayFastStreams : EditorTestScene + { + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var testBeatmap = new TestBeatmap(ruleset, false); + testBeatmap.HitObjects.AddRange(new[] + { + new HitCircle { StartTime = 500 }, + new HitCircle { StartTime = 530 }, + new HitCircle { StartTime = 560 }, + new HitCircle { StartTime = 590 }, + new HitCircle { StartTime = 620 }, + }); + + return testBeatmap; + } + + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + [Test] + public void TestAllHit() + { + AddStep("start playback", () => EditorClock.Start()); + AddUntilStep("wait for all hit", () => + { + DrawableHitCircle[] hitCircles = Editor.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).ToArray(); + + return hitCircles.Length == 5 && hitCircles.All(h => h.IsHit); + }); + } + } +} From 65c29b4f09d49d772cfe2c9934c4d1ee65e9b384 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 13:18:41 +0900 Subject: [PATCH 04/35] Make editor remain frame stable during normal playback --- .../Edit/DrawableEditorRulesetWrapper.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs index 174b278d89..ebf06bcc4e 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; @@ -26,6 +27,9 @@ namespace osu.Game.Rulesets.Edit [Resolved] private EditorBeatmap beatmap { get; set; } = null!; + [Resolved] + private EditorClock editorClock { get; set; } = null!; + public DrawableEditorRulesetWrapper(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; @@ -38,7 +42,6 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load() { - drawableRuleset.FrameStablePlayback = false; Playfield.DisplayJudgements.Value = false; } @@ -65,6 +68,22 @@ namespace osu.Game.Rulesets.Edit Scheduler.AddOnce(regenerateAutoplay); } + protected override void Update() + { + base.Update(); + + // Whenever possible, we want to stay in frame stability playback. + // Without doing so, we run into bugs with some gameplay elements not behaving as expected. + // + // Note that this is not using EditorClock.IsSeeking as that would exit frame stability + // on all seeks. The intention here is to retain frame stability for small seeks. + // + // I still think no gameplay elements should require frame stability in the first place, but maybe that ship has sailed already.. + bool shouldBypassFrameStability = Math.Abs(drawableRuleset.FrameStableClock.CurrentTime - editorClock.CurrentTime) > 1000; + + drawableRuleset.FrameStablePlayback = !shouldBypassFrameStability; + } + private void regenerateAutoplay() { var autoplayMod = drawableRuleset.Mods.OfType().Single(); From 3419c59b062ba0ac9670d9279029d6fbb062aac0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 15:33:18 +0900 Subject: [PATCH 05/35] Fix failing tests due to frame stable seeks taking longer --- .../Editor/TestSceneOsuComposerSelection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index 623cefff6b..366f17daee 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -46,10 +46,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor moveMouseToObject(() => slider); AddStep("seek after end", () => EditorClock.Seek(750)); + AddUntilStep("wait for seek", () => !EditorClock.IsSeeking); AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddAssert("slider not selected", () => EditorBeatmap.SelectedHitObjects.Count == 0); AddStep("seek to visible", () => EditorClock.Seek(650)); + AddUntilStep("wait for seek", () => !EditorClock.IsSeeking); AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddUntilStep("slider selected", () => EditorBeatmap.SelectedHitObjects.Single() == slider); } From adac3b65cea7a896907ed5451abedc1e241e866f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 18:48:13 +0900 Subject: [PATCH 06/35] Fix beatmap carousel not preloading panels when off-screen --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 73 +++++++++++++------ 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 89911c9a69..3353aeed7d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -96,12 +96,12 @@ namespace osu.Game.Screens.Select /// /// Extend the range to retain already loaded pooled drawables. /// - private const float distance_offscreen_before_unload = 1024; + private const float distance_offscreen_before_unload = 2048; /// /// Extend the range to update positions / retrieve pooled drawables outside of visible range. /// - private const float distance_offscreen_to_preload = 512; // todo: adjust this appropriately once we can make set panel contents load while off-screen. + private const float distance_offscreen_to_preload = 768; /// /// Whether carousel items have completed asynchronously loaded. diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index f16e92a82a..c24e09582e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -46,6 +47,8 @@ namespace osu.Game.Screens.Select.Carousel private MenuItem[]? mainMenuItems; + private double timeSinceUnpool; + [Resolved] private BeatmapManager manager { get; set; } = null!; @@ -54,6 +57,7 @@ namespace osu.Game.Screens.Select.Carousel base.FreeAfterUse(); Item = null; + timeSinceUnpool = 0; ClearTransforms(); } @@ -92,13 +96,21 @@ namespace osu.Game.Screens.Select.Carousel // algorithm for this is taken from ScrollContainer. // while it doesn't necessarily need to match 1:1, as we are emulating scroll in some cases this feels most correct. Y = (float)Interpolation.Lerp(targetY, Y, Math.Exp(-0.01 * Time.Elapsed)); + + loadContentIfRequired(); } + private CancellationTokenSource? loadCancellation; + protected override void UpdateItem() { + loadCancellation?.Cancel(); + loadCancellation = null; + base.UpdateItem(); Content.Clear(); + Header.Clear(); beatmapContainer = null; beatmapsLoadTask = null; @@ -107,32 +119,8 @@ namespace osu.Game.Screens.Select.Carousel return; beatmapSet = ((CarouselBeatmapSet)Item).BeatmapSet; - - DelayedLoadWrapper background; - DelayedLoadWrapper mainFlow; - - Header.Children = new Drawable[] - { - // Choice of background image matches BSS implementation (always uses the lowest `beatmap_id` from the set). - background = new DelayedLoadWrapper(() => new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) - { - RelativeSizeAxes = Axes.Both, - }, 200) - { - RelativeSizeAxes = Axes.Both - }, - mainFlow = new DelayedLoadWrapper(() => new SetPanelContent((CarouselBeatmapSet)Item), 50) - { - RelativeSizeAxes = Axes.Both - }, - }; - - background.DelayedLoadComplete += fadeContentIn; - mainFlow.DelayedLoadComplete += fadeContentIn; } - private void fadeContentIn(Drawable d) => d.FadeInFromZero(150); - protected override void Deselected() { base.Deselected(); @@ -190,6 +178,43 @@ namespace osu.Game.Screens.Select.Carousel } } + private void loadContentIfRequired() + { + // Using DelayedLoadWrappers would only allow us to load content when on screen, but we want to preload while off-screen + // to provide a better user experience. + + // This is tracking time that this drawable is updating since the last pool. + // This is intended to provide a debounce so very fast scrolls (from one end to the other of the carousel) + // don't cause huge overheads. + const double time_updating_before_load = 150; + + Debug.Assert(Item != null); + + if (loadCancellation == null && (timeSinceUnpool += Time.Elapsed) > time_updating_before_load) + { + loadCancellation = new CancellationTokenSource(); + + LoadComponentAsync(new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) + { + RelativeSizeAxes = Axes.Both, + }, background => + { + Header.Add(background); + background.FadeInFromZero(150); + }, loadCancellation.Token); + + LoadComponentAsync(new SetPanelContent((CarouselBeatmapSet)Item) + { + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + }, mainFlow => + { + Header.Add(mainFlow); + mainFlow.FadeInFromZero(150); + }, loadCancellation.Token); + } + } + private void updateBeatmapYPositions() { if (beatmapContainer == null) From 81c6fd5589751e125a8728ad57eb286dc4b146f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jan 2024 19:13:36 +0900 Subject: [PATCH 07/35] Load items closer to the centre of the screen as a priority --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 41 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3353aeed7d..4408634787 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -108,6 +108,7 @@ namespace osu.Game.Screens.Select /// public bool BeatmapSetsLoaded { get; private set; } + [Cached] protected readonly CarouselScrollContainer Scroll; private readonly NoResultsPlaceholder noResultsPlaceholder; @@ -1251,7 +1252,7 @@ namespace osu.Game.Screens.Select } } - protected partial class CarouselScrollContainer : UserTrackingScrollContainer + public partial class CarouselScrollContainer : UserTrackingScrollContainer { private bool rightMouseScrollBlocked; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index c24e09582e..61658526db 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -8,9 +8,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -178,39 +180,44 @@ namespace osu.Game.Screens.Select.Carousel } } + [Resolved] + private BeatmapCarousel.CarouselScrollContainer scrollContainer { get; set; } = null!; + private void loadContentIfRequired() { + Quad containingSsdq = scrollContainer.ScreenSpaceDrawQuad; + // Using DelayedLoadWrappers would only allow us to load content when on screen, but we want to preload while off-screen // to provide a better user experience. // This is tracking time that this drawable is updating since the last pool. // This is intended to provide a debounce so very fast scrolls (from one end to the other of the carousel) // don't cause huge overheads. - const double time_updating_before_load = 150; + // + // We increase the delay based on distance from centre, so the beatmaps the user is currently looking at load first. + float timeUpdatingBeforeLoad = 50 + Math.Abs(containingSsdq.Centre.Y - ScreenSpaceDrawQuad.Centre.Y) / containingSsdq.Height * 100; Debug.Assert(Item != null); - if (loadCancellation == null && (timeSinceUnpool += Time.Elapsed) > time_updating_before_load) + if (loadCancellation == null && (timeSinceUnpool += Time.Elapsed) > timeUpdatingBeforeLoad) { loadCancellation = new CancellationTokenSource(); - LoadComponentAsync(new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) + LoadComponentsAsync(new CompositeDrawable[] { - RelativeSizeAxes = Axes.Both, - }, background => + new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) + { + RelativeSizeAxes = Axes.Both, + }, + new SetPanelContent((CarouselBeatmapSet)Item) + { + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + } + }, drawables => { - Header.Add(background); - background.FadeInFromZero(150); - }, loadCancellation.Token); - - LoadComponentAsync(new SetPanelContent((CarouselBeatmapSet)Item) - { - Depth = float.MinValue, - RelativeSizeAxes = Axes.Both, - }, mainFlow => - { - Header.Add(mainFlow); - mainFlow.FadeInFromZero(150); + Header.AddRange(drawables); + drawables.ForEach(d => d.FadeInFromZero(150)); }, loadCancellation.Token); } } From 9b734bac2515c5a8cf61ca3cc7576bf1e012a4b9 Mon Sep 17 00:00:00 2001 From: Zachary Date: Fri, 5 Jan 2024 01:14:34 +1000 Subject: [PATCH 08/35] Allow track control after intro screen finishes. --- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Screens/Menu/MainMenu.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0986c0513c..01086d3e33 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -43,7 +43,7 @@ namespace osu.Game.Overlays /// /// Whether user control of the global track should be allowed. /// - public readonly BindableBool AllowTrackControl = new BindableBool(true); + public readonly BindableBool AllowTrackControl = new BindableBool(false); /// /// Fired when the global has changed. diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 516b090a16..ffb68367c2 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -241,6 +241,8 @@ namespace osu.Game.Screens.Menu { var track = musicController.CurrentTrack; + musicController.AllowTrackControl.Value = true; + // presume the track is the current beatmap's track. not sure how correct this assumption is but it has worked until now. if (!track.IsRunning) { From b190333c17f3e0209e7f04ad1463b272b957c774 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 4 Jan 2024 09:00:24 -0800 Subject: [PATCH 09/35] Use repeat step for more delay between the two exits --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index d8dc512787..a0069f55c7 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -799,11 +799,7 @@ namespace osu.Game.Tests.Visual.Navigation }); }); - AddStep("attempt exit", () => - { - for (int i = 0; i < 2; ++i) - Game.ScreenStack.CurrentScreen.Exit(); - }); + AddRepeatStep("attempt force exit", () => Game.ScreenStack.CurrentScreen.Exit(), 2); AddUntilStep("stopped at exit confirm", () => Game.ChildrenOfType().Single().CurrentDialog is ConfirmExitDialog); } From 091241634c08aaea3b6d3e34762fb2f1cb459295 Mon Sep 17 00:00:00 2001 From: Zachary Date: Fri, 5 Jan 2024 23:55:17 +1000 Subject: [PATCH 10/35] Make IntroScreen set `AllowTrackControl` to false instead --- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 01086d3e33..0986c0513c 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -43,7 +43,7 @@ namespace osu.Game.Overlays /// /// Whether user control of the global track should be allowed. /// - public readonly BindableBool AllowTrackControl = new BindableBool(false); + public readonly BindableBool AllowTrackControl = new BindableBool(true); /// /// Fired when the global has changed. diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index de7732dd5e..a81e24ee88 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -109,6 +109,8 @@ namespace osu.Game.Screens.Menu // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); + musicController.AllowTrackControl.Value = false; + MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); From e2769dbda14b21e550c3bafa9446af1e406e2966 Mon Sep 17 00:00:00 2001 From: Zachary Date: Sat, 6 Jan 2024 19:29:41 +1000 Subject: [PATCH 11/35] Attempt at creating a test. --- .../TestSceneIntroMusicActionHandling.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs new file mode 100644 index 0000000000..cc2b16a842 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs @@ -0,0 +1,47 @@ +// 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.Testing; +using osu.Game.Input.Bindings; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + public partial class TestSceneIntroMusicActionHandling : OsuTestScene + { + private OsuGameTestScene.TestOsuGame? game; + + private GlobalActionContainer globalActionContainer => game.ChildrenOfType().First(); + + [Test] + public void TestPauseDuringIntro() + { + AddStep("Create new game instance", () => + { + if (game?.Parent != null) + Remove(game, true); + + RecycleLocalStorage(false); + + AddGame(game = new OsuGameTestScene.TestOsuGame(LocalStorage, API)); + }); + + AddUntilStep("Wait for load", () => game?.IsLoaded ?? false); + AddUntilStep("Wait for intro", () => game?.ScreenStack.CurrentScreen is IntroScreen); + AddUntilStep("Wait for music", () => game?.MusicController.IsPlaying == true); + + // Check that pause dosesn't work during intro sequence. + AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); + AddAssert("Still playing before menu", () => game?.MusicController.IsPlaying == true); + AddUntilStep("Wait for main menu", () => game?.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); + + // Check that toggling after intro still works. + AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); + AddUntilStep("Music paused", () => game?.MusicController.IsPlaying == false && game?.MusicController.UserPauseRequested == true); + AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); + AddUntilStep("Music resumed", () => game?.MusicController.IsPlaying == true && game?.MusicController.UserPauseRequested == false); + } + } +} From 14a43375a70104094f7a9d23cf091c18d1223ad3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 6 Jan 2024 20:25:03 +0900 Subject: [PATCH 12/35] Fix overall ranking text overlapping at some aspect ratios Can't confirm on the actual ranking screen due to stuff not working. Maybe it'll work tomorrow. Closes https://github.com/ppy/osu/issues/26341. --- osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs index 73b9897096..762be61853 100644 --- a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs @@ -37,7 +37,6 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.X, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Width = 0.5f, StatisticsUpdate = { BindTarget = StatisticsUpdate } })).ToArray(); } From d3710f0bfd0e8fb457ffbde1fb14dce853ad4f14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 6 Jan 2024 20:44:07 +0900 Subject: [PATCH 13/35] Remove scores from song select leaderboard when leaving the screen --- osu.Game/Online/Leaderboards/Leaderboard.cs | 13 ++++++++++--- osu.Game/Screens/Select/PlaySongSelect.cs | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 93aa0b95a7..67f2590ad8 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -152,6 +152,15 @@ namespace osu.Game.Online.Leaderboards /// public void RefetchScores() => Scheduler.AddOnce(refetchScores); + /// + /// Clear all scores from the display. + /// + public void ClearScores() + { + cancelPendingWork(); + SetScores(null); + } + /// /// Call when a retrieval or display failure happened to show a relevant message to the user. /// @@ -220,9 +229,7 @@ namespace osu.Game.Online.Leaderboards { Debug.Assert(ThreadSafety.IsUpdateThread); - cancelPendingWork(); - - SetScores(null); + ClearScores(); setState(LeaderboardState.Retrieving); currentFetchCancellationSource = new CancellationTokenSource(); diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 7b7b8857f3..4951504ff5 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -146,6 +146,14 @@ namespace osu.Game.Screens.Select } } + public override void OnSuspending(ScreenTransitionEvent e) + { + // Scores will be refreshed on arriving at this screen. + // Clear them to avoid animation overload on returning to song select. + playBeatmapDetailArea.Leaderboard.ClearScores(); + base.OnSuspending(e); + } + public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); From 7b663a27bd4d37334e428f1afc3179bc8f9da7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 10:47:22 +0100 Subject: [PATCH 14/35] Fix score conversion incorrectly assuming zero combo score in certain cases --- .../StandardisedScoreMigrationTools.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 9cfb9ea957..24fe147593 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -311,13 +311,22 @@ namespace osu.Game.Database long maximumLegacyBonusScore = attributes.BonusScore; double legacyAccScore = maximumLegacyAccuracyScore * score.Accuracy; - // We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio. - // Note that `maximumLegacyComboScore + maximumLegacyBonusScore` can actually be 0 - // when playing a beatmap with no bonus objects, with mods that have a 0.0x multiplier on stable (relax/autopilot). - // In such cases, just assume 0. - double comboProportion = maximumLegacyComboScore + maximumLegacyBonusScore > 0 - ? Math.Max((double)score.LegacyTotalScore - legacyAccScore, 0) / (maximumLegacyComboScore + maximumLegacyBonusScore) - : 0; + + double comboProportion; + + if (maximumLegacyComboScore + maximumLegacyBonusScore > 0) + { + // We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio. + comboProportion = Math.Max((double)score.LegacyTotalScore - legacyAccScore, 0) / (maximumLegacyComboScore + maximumLegacyBonusScore); + } + else + { + // Two possible causes: + // the beatmap has no bonus objects *AND* + // either the active mods have a zero mod multiplier, in which case assume 0, + // or the *beatmap* has a zero `difficultyPeppyStars` (or just no combo-giving objects), in which case assume 1. + comboProportion = legacyModMultiplier == 0 ? 0 : 1; + } // We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore. long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore; From 50eba9ebdb92790c10fda1904219525029abe0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 12:52:14 +0100 Subject: [PATCH 15/35] Reduce code duplication in test --- .../TestSceneIntroMusicActionHandling.cs | 32 +++++++------------ osu.Game/Tests/Visual/OsuGameTestScene.cs | 8 +++-- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs index cc2b16a842..00c14dc797 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs @@ -9,39 +9,31 @@ using osu.Game.Screens.Menu; namespace osu.Game.Tests.Visual.Menus { - public partial class TestSceneIntroMusicActionHandling : OsuTestScene + public partial class TestSceneIntroMusicActionHandling : OsuGameTestScene { - private OsuGameTestScene.TestOsuGame? game; + private GlobalActionContainer globalActionContainer => Game.ChildrenOfType().First(); - private GlobalActionContainer globalActionContainer => game.ChildrenOfType().First(); + public override void SetUpSteps() + { + CreateNewGame(); + // we do not want to progress to main menu immediately, hence the override and lack of `ConfirmAtMainMenu()` call here. + } [Test] public void TestPauseDuringIntro() { - AddStep("Create new game instance", () => - { - if (game?.Parent != null) - Remove(game, true); - - RecycleLocalStorage(false); - - AddGame(game = new OsuGameTestScene.TestOsuGame(LocalStorage, API)); - }); - - AddUntilStep("Wait for load", () => game?.IsLoaded ?? false); - AddUntilStep("Wait for intro", () => game?.ScreenStack.CurrentScreen is IntroScreen); - AddUntilStep("Wait for music", () => game?.MusicController.IsPlaying == true); + AddUntilStep("Wait for music", () => Game?.MusicController.IsPlaying == true); // Check that pause dosesn't work during intro sequence. AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); - AddAssert("Still playing before menu", () => game?.MusicController.IsPlaying == true); - AddUntilStep("Wait for main menu", () => game?.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); + AddAssert("Still playing before menu", () => Game?.MusicController.IsPlaying == true); + AddUntilStep("Wait for main menu", () => Game?.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); // Check that toggling after intro still works. AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); - AddUntilStep("Music paused", () => game?.MusicController.IsPlaying == false && game?.MusicController.UserPauseRequested == true); + AddUntilStep("Music paused", () => Game?.MusicController.IsPlaying == false && Game?.MusicController.UserPauseRequested == true); AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); - AddUntilStep("Music resumed", () => game?.MusicController.IsPlaying == true && game?.MusicController.UserPauseRequested == false); + AddUntilStep("Music resumed", () => Game?.MusicController.IsPlaying == true && Game?.MusicController.UserPauseRequested == false); } } } diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index 94be4a375d..947305439e 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -58,6 +58,12 @@ namespace osu.Game.Tests.Visual [SetUpSteps] public virtual void SetUpSteps() + { + CreateNewGame(); + ConfirmAtMainMenu(); + } + + protected void CreateNewGame() { AddStep("Create new game instance", () => { @@ -71,8 +77,6 @@ namespace osu.Game.Tests.Visual AddUntilStep("Wait for load", () => Game.IsLoaded); AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroScreen); - - ConfirmAtMainMenu(); } [TearDownSteps] From b869be2f463b41781bfaefd2926c54925a33ac03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 12:52:23 +0100 Subject: [PATCH 16/35] Fix typo --- .../Visual/Menus/TestSceneIntroMusicActionHandling.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs index 00c14dc797..01aeaff1db 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroMusicActionHandling.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Menus { AddUntilStep("Wait for music", () => Game?.MusicController.IsPlaying == true); - // Check that pause dosesn't work during intro sequence. + // Check that pause doesn't work during intro sequence. AddStep("Toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); AddAssert("Still playing before menu", () => Game?.MusicController.IsPlaying == true); AddUntilStep("Wait for main menu", () => Game?.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); From b6ce57b777983af3808b9736d86d1aba4b6faf80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 12:54:16 +0100 Subject: [PATCH 17/35] Use override that was intended to steer global track control rather than local sets --- osu.Game/Screens/Menu/IntroScreen.cs | 4 ++-- osu.Game/Screens/Menu/MainMenu.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index a81e24ee88..ac7dffc241 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -95,6 +95,8 @@ namespace osu.Game.Screens.Menu Colour = Color4.Black }; + public override bool? AllowGlobalTrackControl => false; + protected IntroScreen([CanBeNull] Func createNextScreen = null) { this.createNextScreen = createNextScreen; @@ -109,8 +111,6 @@ namespace osu.Game.Screens.Menu // prevent user from changing beatmap while the intro is still running. beatmap = Beatmap.BeginLease(false); - musicController.AllowTrackControl.Value = false; - MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index ffb68367c2..b264341cc5 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -49,6 +49,8 @@ namespace osu.Game.Screens.Menu public override bool AllowExternalScreenChange => true; + public override bool? AllowGlobalTrackControl => true; + private Screen songSelect; private MenuSideFlashes sideFlashes; @@ -241,8 +243,6 @@ namespace osu.Game.Screens.Menu { var track = musicController.CurrentTrack; - musicController.AllowTrackControl.Value = true; - // presume the track is the current beatmap's track. not sure how correct this assumption is but it has worked until now. if (!track.IsRunning) { From e77d203a24088174a47810a70112e2a4cda46fc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 01:08:17 +0900 Subject: [PATCH 18/35] Refactor delayed load logic to hopefully read better --- .../Carousel/DrawableCarouselBeatmapSet.cs | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 61658526db..b70278c9bb 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -199,27 +199,34 @@ namespace osu.Game.Screens.Select.Carousel Debug.Assert(Item != null); - if (loadCancellation == null && (timeSinceUnpool += Time.Elapsed) > timeUpdatingBeforeLoad) - { - loadCancellation = new CancellationTokenSource(); + // A load is already in progress if the cancellation token is non-null. + if (loadCancellation != null) + return; - LoadComponentsAsync(new CompositeDrawable[] + timeSinceUnpool += Time.Elapsed; + + // We only trigger a load after this set has been in an updating state for a set amount of time. + if (timeSinceUnpool <= timeUpdatingBeforeLoad) + return; + + loadCancellation = new CancellationTokenSource(); + + LoadComponentsAsync(new CompositeDrawable[] + { + new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) { - new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) - { - RelativeSizeAxes = Axes.Both, - }, - new SetPanelContent((CarouselBeatmapSet)Item) - { - Depth = float.MinValue, - RelativeSizeAxes = Axes.Both, - } - }, drawables => + RelativeSizeAxes = Axes.Both, + }, + new SetPanelContent((CarouselBeatmapSet)Item) { - Header.AddRange(drawables); - drawables.ForEach(d => d.FadeInFromZero(150)); - }, loadCancellation.Token); - } + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + } + }, drawables => + { + Header.AddRange(drawables); + drawables.ForEach(d => d.FadeInFromZero(150)); + }, loadCancellation.Token); } private void updateBeatmapYPositions() From 51bd32bf7e94e74967d6852ead980b71bed6a8e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 01:08:47 +0900 Subject: [PATCH 19/35] Restore comment regarding usage of `MinBy` --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index b70278c9bb..369db37e63 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -213,6 +213,7 @@ namespace osu.Game.Screens.Select.Carousel LoadComponentsAsync(new CompositeDrawable[] { + // Choice of background image matches BSS implementation (always uses the lowest `beatmap_id` from the set). new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID))) { RelativeSizeAxes = Axes.Both, From 8a87301c55262f888017cb2ce5ed23d04429ab05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 21:33:25 +0100 Subject: [PATCH 20/35] Add test for crashing scenario --- .../Navigation/TestSceneScreenNavigation.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index a0069f55c7..8cb993eff2 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -938,6 +938,35 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("touch device mod still active", () => Game.SelectedMods.Value, () => Has.One.InstanceOf()); } + [Test] + public void TestExitSongSelectAndImmediatelyClickLogo() + { + Screens.Select.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("press escape and then click logo immediately", () => + { + InputManager.Key(Key.Escape); + clickLogoWhenNotCurrent(); + }); + + void clickLogoWhenNotCurrent() + { + if (songSelect.IsCurrentScreen()) + Scheduler.AddOnce(clickLogoWhenNotCurrent); + else + { + InputManager.MoveMouseTo(Game.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + } + } + } + private Func playToResults() { var player = playToCompletion(); From 58db39ec3206a34e1b8a0cbdbe196059dd4b8306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 21:37:25 +0100 Subject: [PATCH 21/35] Fix crash when clicking osu! logo in song select immediately after exiting Closes https://github.com/ppy/osu/issues/26415. The crash report with incomplete log was backwards, the exit comes first. Sentry events and the reproducing test in 8a87301c55262f888017cb2ce5ed23d04429ab05 confirm this. --- osu.Game/Screens/Select/PlaySongSelect.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 4951504ff5..3cf8de5267 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -92,6 +92,9 @@ namespace osu.Game.Screens.Select { if (playerLoader != null) return false; + if (!this.IsCurrentScreen()) + return false; + modsAtGameplayStart = Mods.Value; // Ctrl+Enter should start map with autoplay enabled. From 67df7b33fb6c355ad41382fae8323c5c9e3d0c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 21:51:01 +0100 Subject: [PATCH 22/35] Add failing test coverage for not attempting to upgrade custom ruleset scores --- .../BackgroundDataStoreProcessorTests.cs | 30 +++++++++++++++++++ osu.Game/BackgroundDataStoreProcessor.cs | 4 ++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 43ce7200d2..9dbfde7bce 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.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 System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -182,9 +183,38 @@ namespace osu.Game.Tests.Database AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002)); } + [Test] + public void TestCustomRulesetScoreNotSubjectToUpgrades([Values] bool available) + { + RulesetInfo rulesetInfo = null!; + ScoreInfo scoreInfo = null!; + TestBackgroundDataStoreProcessor processor = null!; + + AddStep("Add unavailable ruleset", () => Realm.Write(r => r.Add(rulesetInfo = new RulesetInfo + { + ShortName = Guid.NewGuid().ToString(), + Available = available + }))); + + AddStep("Add score for unavailable ruleset", () => Realm.Write(r => r.Add(scoreInfo = new ScoreInfo( + ruleset: rulesetInfo, + beatmap: r.All().First()) + { + TotalScoreVersion = 30000001 + }))); + + AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor())); + AddUntilStep("Wait for completion", () => processor.Completed); + + AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False); + AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000001)); + } + public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor { protected override int TimeToSleepDuringGameplay => 10; + + public bool Completed => ProcessingTask.IsCompleted; } } } diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index a748a7422a..969062be57 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -28,6 +28,8 @@ namespace osu.Game /// public partial class BackgroundDataStoreProcessor : Component { + protected Task ProcessingTask = null!; + [Resolved] private RulesetStore rulesetStore { get; set; } = null!; @@ -61,7 +63,7 @@ namespace osu.Game { base.LoadComplete(); - Task.Factory.StartNew(() => + ProcessingTask = Task.Factory.StartNew(() => { Logger.Log("Beginning background data store processing.."); From 388f6599e0264d640d4da1fda0e27441212ca389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 21:54:10 +0100 Subject: [PATCH 23/35] Add failing test for not attempting to upgrade non-legacy scores This was a source of confusion for users previously, wondering why their non-legacy (non-stable) scores weren't being converted in line with new scoring changes, when it was never actually our intention to support anything of the sort. --- .../BackgroundDataStoreProcessorTests.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 9dbfde7bce..8b066f860f 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -210,6 +210,31 @@ namespace osu.Game.Tests.Database AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000001)); } + [Test] + public void TestNonLegacyScoreNotSubjectToUpgrades() + { + ScoreInfo scoreInfo = null!; + TestBackgroundDataStoreProcessor processor = null!; + + AddStep("Add score which requires upgrade (and has beatmap)", () => + { + Realm.Write(r => + { + r.Add(scoreInfo = new ScoreInfo(ruleset: r.All().First(), beatmap: r.All().First()) + { + TotalScoreVersion = 30000005, + LegacyTotalScore = 123456, + }); + }); + }); + + AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor())); + AddUntilStep("Wait for completion", () => processor.Completed); + + AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False); + AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000005)); + } + public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor { protected override int TimeToSleepDuringGameplay => 10; From aa83b84bb225781adccc15f4276a74901ed03397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 22:34:41 +0100 Subject: [PATCH 24/35] Fix Cinema mod being compatible with mods that can force failure Addresses https://github.com/ppy/osu/pull/26080#issuecomment-1868833214. --- osu.Game/Rulesets/Mods/ModCinema.cs | 2 +- osu.Game/Rulesets/Mods/ModFailCondition.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index dbb37e0af6..7c88a8a588 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModCinema; public override LocalisableString Description => "Watch the video without visual distractions."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModAutoplay), typeof(ModNoFail) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) }).ToArray(); public void ApplyToHUD(HUDOverlay overlay) { diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs index 471c3bfe8d..0b229766c1 100644 --- a/osu.Game/Rulesets/Mods/ModFailCondition.cs +++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride { - public override Type[] IncompatibleMods => new[] { typeof(ModNoFail) }; + public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModCinema) }; [SettingSource("Restart on fail", "Automatically restarts when failed.")] public BindableBool Restart { get; } = new BindableBool(); From 4f7dcb3a5022cb533cc467d974a7f382d1cf285e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jan 2024 22:07:33 +0100 Subject: [PATCH 25/35] Do not attempt to recalculate non-legacy scores or scores set on custom rulesets Addresses discussions such as https://github.com/ppy/osu/discussions/26407 or https://github.com/ppy/osu/discussions/25914 wherein: - the game would attempt to convert scores for custom rulesets, which makes no sense, especially so when they're not there, - the game would also "recalculate" lazer scores, but that was never the intention or was never supported; the game would just increment the score version on those but still include them in the converted tally. --- osu.Game/BackgroundDataStoreProcessor.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index 969062be57..fc7db13d41 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -28,7 +29,7 @@ namespace osu.Game /// public partial class BackgroundDataStoreProcessor : Component { - protected Task ProcessingTask = null!; + protected Task ProcessingTask { get; private set; } = null!; [Resolved] private RulesetStore rulesetStore { get; set; } = null!; @@ -316,10 +317,17 @@ namespace osu.Game { Logger.Log("Querying for scores that need total score conversion..."); - HashSet scoreIds = realmAccess.Run(r => new HashSet(r.All() - .Where(s => !s.BackgroundReprocessingFailed && s.BeatmapInfo != null - && s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION) - .AsEnumerable().Select(s => s.ID))); + HashSet scoreIds = realmAccess.Run(r => new HashSet( + r.All() + .Where(s => !s.BackgroundReprocessingFailed + && s.BeatmapInfo != null + && s.IsLegacyScore + && s.TotalScoreVersion < LegacyScoreEncoder.LATEST_VERSION) + .AsEnumerable() + // must be done after materialisation, as realm doesn't want to support + // nested property predicates + .Where(s => s.Ruleset.IsLegacyRuleset()) + .Select(s => s.ID))); Logger.Log($"Found {scoreIds.Count} scores which require total score conversion."); From 58619f168458ead922914536a651951595f33875 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 8 Jan 2024 14:16:05 -0800 Subject: [PATCH 26/35] Fix wiki main page not displaying custom layout --- osu.Game/Overlays/WikiOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 440e451201..a8d9cdcdb2 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -137,7 +137,7 @@ namespace osu.Game.Overlays wikiData.Value = response; path.Value = response.Path; - if (response.Layout == index_path) + if (response.Layout == index_path.ToLowerInvariant()) { LoadDisplay(new WikiMainPage { From d6ba7a9c6eeab7c2c4889249df4f7280f28515d2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 8 Jan 2024 14:24:56 -0800 Subject: [PATCH 27/35] Centralise `INDEX_PATH` to `WikiOverlay` --- osu.Game/Overlays/Wiki/WikiHeader.cs | 4 +--- osu.Game/Overlays/WikiOverlay.cs | 10 +++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Wiki/WikiHeader.cs b/osu.Game/Overlays/Wiki/WikiHeader.cs index 55be05ed7a..d64d6b934a 100644 --- a/osu.Game/Overlays/Wiki/WikiHeader.cs +++ b/osu.Game/Overlays/Wiki/WikiHeader.cs @@ -17,8 +17,6 @@ namespace osu.Game.Overlays.Wiki { public partial class WikiHeader : BreadcrumbControlOverlayHeader { - private const string index_path = "Main_page"; - public static LocalisableString IndexPageString => LayoutStrings.HeaderHelpIndex; public readonly Bindable WikiPageData = new Bindable(); @@ -45,7 +43,7 @@ namespace osu.Game.Overlays.Wiki TabControl.AddItem(IndexPageString); - if (e.NewValue.Path == index_path) + if (e.NewValue.Path == WikiOverlay.INDEX_PATH) { Current.Value = IndexPageString; return; diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index a8d9cdcdb2..3777e83cde 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -19,11 +19,11 @@ namespace osu.Game.Overlays { public partial class WikiOverlay : OnlineOverlay { - private const string index_path = "Main_page"; + public const string INDEX_PATH = @"Main_page"; public string CurrentPath => path.Value; - private readonly Bindable path = new Bindable(index_path); + private readonly Bindable path = new Bindable(INDEX_PATH); private readonly Bindable wikiData = new Bindable(); @@ -43,7 +43,7 @@ namespace osu.Game.Overlays { } - public void ShowPage(string pagePath = index_path) + public void ShowPage(string pagePath = INDEX_PATH) { path.Value = pagePath.Trim('/'); Show(); @@ -137,7 +137,7 @@ namespace osu.Game.Overlays wikiData.Value = response; path.Value = response.Path; - if (response.Layout == index_path.ToLowerInvariant()) + if (response.Layout == INDEX_PATH.ToLowerInvariant()) { LoadDisplay(new WikiMainPage { @@ -161,7 +161,7 @@ namespace osu.Game.Overlays path.Value = "error"; LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/", - $"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page](Main_page).")); + $"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page]({INDEX_PATH}).")); } private void showParentPage() From b03813d3b40e0a279bb8ef6800cd587162adf0f2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 8 Jan 2024 14:36:08 -0800 Subject: [PATCH 28/35] Update casing of hardcoded "Main_page" string in tests --- osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs | 4 ++-- osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs | 2 +- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 4 ++-- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs index 4e71c5977e..40eda3f3dc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs @@ -24,8 +24,8 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly Bindable wikiPageData = new Bindable(new APIWikiPage { - Title = "Main Page", - Path = "Main_Page", + Title = "Main page", + Path = "Main_page", }); private TestHeader header; diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs index 9967be73e8..7b4eadd46d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online }; } - // From https://osu.ppy.sh/api/v2/wiki/en/Main_Page + // From https://osu.ppy.sh/api/v2/wiki/en/Main_page private const string main_page_markdown = "---\nlayout: main_page\n---\n\n\n\n
\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n
\n\n
\n
\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n
\n
\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n
\n
\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n
\n
\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n
\n
\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n
\n
\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n
\n
\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n
\n
\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n
\n
\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n
\n
\n"; } diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 6c87553971..8909305602 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -69,8 +69,8 @@ namespace osu.Game.Tests.Visual.Online { AddStep("set current path", () => markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/"); - AddStep("set '/wiki/Main_Page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_Page)"); - AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_Page"); + AddStep("set '/wiki/Main_page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_page)"); + AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_page"); AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)"); AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/FAQ"); diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 79c7e3a22e..352f100f3b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -107,12 +107,12 @@ namespace osu.Game.Tests.Visual.Online }; }); - // From https://osu.ppy.sh/api/v2/wiki/en/Main_Page + // From https://osu.ppy.sh/api/v2/wiki/en/Main_page private APIWikiPage responseMainPage => new APIWikiPage { - Title = "Main Page", + Title = "Main page", Layout = "main_page", - Path = "Main_Page", + Path = "Main_page", Locale = "en", Subtitle = null, Markdown = From 7d57a668aba2032be4c000561be60f17674c4732 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 13:12:54 +0900 Subject: [PATCH 29/35] Use main page constant in more places --- osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs index 40eda3f3dc..d259322d4a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiHeader.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Online private readonly Bindable wikiPageData = new Bindable(new APIWikiPage { Title = "Main page", - Path = "Main_page", + Path = WikiOverlay.INDEX_PATH, }); private TestHeader header; diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 352f100f3b..8765d8485a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -11,6 +11,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Overlays.Wiki; namespace osu.Game.Tests.Visual.Online { @@ -111,8 +112,8 @@ namespace osu.Game.Tests.Visual.Online private APIWikiPage responseMainPage => new APIWikiPage { Title = "Main page", - Layout = "main_page", - Path = "Main_page", + Layout = WikiOverlay.INDEX_PATH.ToLowerInvariant(), // custom classes are always lower snake. + Path = WikiOverlay.INDEX_PATH, Locale = "en", Subtitle = null, Markdown = From 172fe53099bf4e2e26e7acc9dc0dc7708b0ea39d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 13:13:32 +0900 Subject: [PATCH 30/35] Use better method of ignore case comparison --- osu.Game/Overlays/WikiOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 3777e83cde..ffbc168fb7 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -137,7 +137,7 @@ namespace osu.Game.Overlays wikiData.Value = response; path.Value = response.Path; - if (response.Layout == INDEX_PATH.ToLowerInvariant()) + if (response.Layout.Equals(INDEX_PATH, StringComparison.OrdinalIgnoreCase)) { LoadDisplay(new WikiMainPage { From 79ff767eba341cba9e4c81e3410eacf75eb4cd07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 14:51:30 +0900 Subject: [PATCH 31/35] Remove unused using --- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 8765d8485a..e70d35f74a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -11,7 +11,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; -using osu.Game.Overlays.Wiki; namespace osu.Game.Tests.Visual.Online { From 19d1fff5362b3652eb70c366f7ea361f89af4737 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 15:37:29 +0900 Subject: [PATCH 32/35] Use native query to avoid huge overheads when cleaning up realm files --- osu.Game/Database/RealmFileStore.cs | 9 +++------ osu.Game/Models/RealmFile.cs | 4 ++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 1da64d5be8..f1ed3f4b63 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.IO; using System.Linq; using osu.Framework.Extensions; @@ -98,15 +99,11 @@ namespace osu.Game.Database // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. 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 = r.All().ToList(); - - foreach (var file in files) + foreach (var file in r.All().Filter("Usages.@count = 0")) { totalFiles++; - if (file.BacklinksCount > 0) - continue; + Debug.Assert(file.BacklinksCount == 0); try { diff --git a/osu.Game/Models/RealmFile.cs b/osu.Game/Models/RealmFile.cs index 2faa3f0ca6..4d1642fb5f 100644 --- a/osu.Game/Models/RealmFile.cs +++ b/osu.Game/Models/RealmFile.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.Linq; using osu.Game.IO; using Realms; @@ -11,5 +12,8 @@ namespace osu.Game.Models { [PrimaryKey] public string Hash { get; set; } = string.Empty; + + [Backlink(nameof(RealmNamedFileUsage.File))] + public IQueryable Usages { get; } = null!; } } From 66b3945cd644abd7704adde5284211768981a36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jan 2024 10:44:30 +0100 Subject: [PATCH 33/35] Move current screen check to better place --- osu.Game/Screens/Select/PlaySongSelect.cs | 3 --- osu.Game/Screens/Select/SongSelect.cs | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 3cf8de5267..4951504ff5 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -92,9 +92,6 @@ namespace osu.Game.Screens.Select { if (playerLoader != null) return false; - if (!this.IsCurrentScreen()) - return false; - modsAtGameplayStart = Mods.Value; // Ctrl+Enter should start map with autoplay enabled. diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2d5c44e5a5..bf1724995a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -660,7 +660,8 @@ namespace osu.Game.Screens.Select logo.Action = () => { - FinaliseSelection(); + if (this.IsCurrentScreen()) + FinaliseSelection(); return false; }; } From cac0b0de6dba025e99692d208cd56f545bb05660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jan 2024 11:38:01 +0100 Subject: [PATCH 34/35] Remove unused using directive --- osu.Game/Database/RealmFileStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index f1ed3f4b63..1bd22af4c7 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; using System.IO; -using System.Linq; using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Logging; From a8a70be04ab13f0ad4c565b86ee206c19ba01d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jan 2024 11:49:42 +0100 Subject: [PATCH 35/35] Reference property via `nameof` rather than hardcoding --- osu.Game/Database/RealmFileStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 1bd22af4c7..9683baec69 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -98,7 +98,7 @@ namespace osu.Game.Database // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. realm.Write(r => { - foreach (var file in r.All().Filter("Usages.@count = 0")) + foreach (var file in r.All().Filter(@$"{nameof(RealmFile.Usages)}.@count = 0")) { totalFiles++;