From d176ce791678c808782c6961dd6b322d254798c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 31 Jul 2025 10:09:05 +0200 Subject: [PATCH 1/5] Ensure scores are re-fetched with correct criteria on re-entering song select Closes https://github.com/ppy/osu/issues/34445. The primary issue is that song select is the only one that supports non-score sort mode, and therefore if any other component changes the sort mode in a way opaque to song select to score, then song select will lose the sort mode because it's using the global leaderboard manager's state which will contain scores sorted by total. Notably, the bug this is fixing requires specific circumstances. For instance, it is not enough to just *start gameplay* for the bug to manifest, because starting gameplay causes a working beatmap refetch: https://github.com/ppy/osu/blob/4b73afd1957a9161e2956fc4191c8114d9958372/osu.Game/Screens/SelectV2/SongSelect.cs#L456-L457 which will trigger a *delayed schedule refetch* of the scores: https://github.com/ppy/osu/blob/d2d3d14f1572ff8fc68fd01ea43c2ef68b5882fa/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs#L235-L253 and because the refetch is thusly delayed, there's a very high chance it *will not run before the screen is resumed* because the wedge will not have its scheduler run until that point in time. This conundrum is also because there is no test coverage for this, because the above makes test coverage setup rather annoying. --- .../Screens/SelectV2/BeatmapDetailsArea.cs | 6 ++++++ .../SelectV2/BeatmapLeaderboardWedge.cs | 18 +++++++++--------- osu.Game/Screens/SelectV2/SongSelect.cs | 2 ++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapDetailsArea.cs b/osu.Game/Screens/SelectV2/BeatmapDetailsArea.cs index 85bbf34837..7a2068b0cf 100644 --- a/osu.Game/Screens/SelectV2/BeatmapDetailsArea.cs +++ b/osu.Game/Screens/SelectV2/BeatmapDetailsArea.cs @@ -97,5 +97,11 @@ namespace osu.Game.Screens.SelectV2 contentContainer.Add(currentContent); currentContent.Show(); } + + public void Refresh() + { + if (currentContent is BeatmapLeaderboardWedge leaderboardWedge) + leaderboardWedge.RefetchScores(); + } } } diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index 259ce8565b..0b845474dd 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -189,14 +189,14 @@ namespace osu.Game.Screens.SelectV2 { base.LoadComplete(); - Scope.BindValueChanged(_ => refetchScores()); - Sorting.BindValueChanged(_ => refetchScores()); - FilterBySelectedMods.BindValueChanged(_ => refetchScores()); - beatmap.BindValueChanged(_ => refetchScores()); - ruleset.BindValueChanged(_ => refetchScores()); + Scope.BindValueChanged(_ => RefetchScores()); + Sorting.BindValueChanged(_ => RefetchScores()); + FilterBySelectedMods.BindValueChanged(_ => RefetchScores()); + beatmap.BindValueChanged(_ => RefetchScores()); + ruleset.BindValueChanged(_ => RefetchScores()); mods.BindValueChanged(_ => refetchScoresFromMods()); - refetchScores(); + RefetchScores(); } protected override void PopIn() @@ -212,14 +212,14 @@ namespace osu.Game.Screens.SelectV2 private void refetchScoresFromMods() { if (FilterBySelectedMods.Value) - refetchScores(); + RefetchScores(); } private bool initialFetchComplete; private ScheduledDelegate? refetchOperation; - private void refetchScores() + public void RefetchScores() { SetScores(Array.Empty()); @@ -477,7 +477,7 @@ namespace osu.Game.Screens.SelectV2 case LeaderboardState.NetworkFailure: return new ClickablePlaceholder(LeaderboardStrings.CouldntFetchScores, FontAwesome.Solid.Sync) { - Action = refetchScores + Action = RefetchScores }; case LeaderboardState.NoneSelected: diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index edc94953da..51150df384 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -595,6 +595,8 @@ namespace osu.Game.Screens.SelectV2 ensureGlobalBeatmapValid(); + detailsArea.Refresh(); + if (ControlGlobalMusic) { // restart playback on returning to song select, regardless. From 7a263efaa2a689e2e539ce5df1527e217cdf82cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 31 Jul 2025 10:10:14 +0200 Subject: [PATCH 2/5] Always fetch leaderboard with score sort mode in solo results This is borderline pedantic and mostly irrelevant but I think it makes some sense for a bit of extra safety. It definitely does not fix any bugs (that I'm aware of). --- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 2d772e5f09..aeb21b09cb 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -41,12 +41,12 @@ namespace osu.Game.Screens.Ranking { Debug.Assert(Score != null); + // sort mode intentionally omitted to default to score - results screen only supports sorting by score, so don't pass any other to avoid confusion var criteria = new LeaderboardCriteria( Score.BeatmapInfo!, Score.Ruleset, leaderboardManager.CurrentCriteria?.Scope ?? BeatmapLeaderboardScope.Global, - leaderboardManager.CurrentCriteria?.ExactMods, - leaderboardManager.CurrentCriteria?.Sorting ?? LeaderboardSortMode.Score + leaderboardManager.CurrentCriteria?.ExactMods ); var requestTaskSource = new TaskCompletionSource(); globalScores.BindValueChanged(_ => From 4e5dfb82ca1f57737bd6dfd3eabe5016d1e1d27c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 3 Aug 2025 01:46:04 +0900 Subject: [PATCH 3/5] Fix missing disposal of Realm subscription --- osu.Game/Online/Leaderboards/LeaderboardManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Online/Leaderboards/LeaderboardManager.cs b/osu.Game/Online/Leaderboards/LeaderboardManager.cs index 88cc9d5db5..5750c83c97 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardManager.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardManager.cs @@ -188,6 +188,13 @@ namespace osu.Game.Online.Leaderboards var newScoresArray = newScores.ToArray(); scores.Value = LeaderboardScores.Success(newScoresArray, newScoresArray.Length, null); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + localScoreSubscription?.Dispose(); + } } public record LeaderboardCriteria( From f5b3990b365b01766e843af4f15d74bd4b085701 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 3 Aug 2025 03:35:43 +0900 Subject: [PATCH 4/5] Fix intemittent update manager test --- osu.Game.Tests/NonVisual/TestSceneUpdateManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/NonVisual/TestSceneUpdateManager.cs b/osu.Game.Tests/NonVisual/TestSceneUpdateManager.cs index 8a9ee4b81b..e20fb50722 100644 --- a/osu.Game.Tests/NonVisual/TestSceneUpdateManager.cs +++ b/osu.Game.Tests/NonVisual/TestSceneUpdateManager.cs @@ -48,6 +48,13 @@ namespace osu.Game.Tests.NonVisual AddUntilStep("no check pending", () => !manager.IsPending); } + [TearDownSteps] + public void TeardownSteps() + { + // Importantly, this immediately saves the config, which cancels any pending background save. + AddStep("dispose config manager", () => config.Dispose()); + } + /// /// Updates should be checked when the release stream is changed. /// From 0270ca6cf14c3ebe0a090f609cabc27f8ca70678 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 3 Aug 2025 03:49:27 +0900 Subject: [PATCH 5/5] Fix skin editor tests not passing on macOS --- .../TestSceneSkinEditorNavigation.cs | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index fe76b74bcb..02b2db6e31 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -126,12 +126,7 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for accuracy counter", () => Game.ChildrenOfType().Any(counter => counter.Position != new Vector2())); AddStep("dump state of accuracy meter", () => state = JsonConvert.SerializeObject(Game.ChildrenOfType().First().CreateSerialisedInfo())); AddStep("add any component", () => Game.ChildrenOfType().First().TriggerClick()); - AddStep("undo", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.Z); - InputManager.ReleaseKey(Key.ControlLeft); - }); + AddStep("undo", () => InputManager.Keys(PlatformAction.Undo)); AddUntilStep("only one accuracy meter left", () => Game.ChildrenOfType().Single().ChildrenOfType().Count(), () => Is.EqualTo(1)); @@ -163,12 +158,7 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for accuracy counter", () => Game.ChildrenOfType().Any(counter => counter.Position != new Vector2())); AddStep("dump state of accuracy meter", () => state = JsonConvert.SerializeObject(Game.ChildrenOfType().First().CreateSerialisedInfo())); AddStep("add any component", () => Game.ChildrenOfType().First().TriggerClick()); - AddStep("undo", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.Z); - InputManager.ReleaseKey(Key.ControlLeft); - }); + AddStep("undo", () => InputManager.Keys(PlatformAction.Undo)); AddUntilStep("only one accuracy meter left", () => Game.ChildrenOfType().Single().ChildrenOfType().Count(), () => Is.EqualTo(1)); @@ -190,12 +180,7 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for components", () => skinEditor.ChildrenOfType().Any()); - AddStep("select all components", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.A); - InputManager.ReleaseKey(Key.ControlLeft); - }); + AddStep("select all components", () => InputManager.Keys(PlatformAction.SelectAll)); AddUntilStep("components selected", () => skinEditor.SelectedComponents.Count > 0);