diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselUpdateHandling.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselUpdateHandling.cs index 1033a17e05cf..e8ade5c63bd6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselUpdateHandling.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselUpdateHandling.cs @@ -430,6 +430,56 @@ public void TestSortingStabilityWithNewItems() AddAssert("Order didn't change", () => Carousel.PostFilterBeatmaps.Select(b => b.ID), () => Is.EqualTo(originalOrder)); } + /// + /// Replicates the #34826 scenario: the matched beatmap is present in the replace snapshot, but has been + /// deleted from realm by the time the replace is processed, see https://github.com/ppy/osu/issues/34826. + /// + [Test] + public void TestBeatmapSetReplacedWithDeletedCurrentBeatmap() + { + int targetSetIndex = 0; + + AddStep("select first difficulty", () => + { + Carousel.CurrentBeatmap = baseTestBeatmap.Beatmaps[0]; + BeatmapRequestedSelections.Clear(); + }); + + AddStep("delete current beatmap from realm and replace set", () => + { + targetSetIndex = BeatmapSets.IndexOf(baseTestBeatmap); + var detachedSet = BeatmapSets[targetSetIndex]; + var selectedBeatmap = detachedSet.Beatmaps[0]; + + Realm.Write(r => + { + var toDelete = r.Find(selectedBeatmap.ID); + if (toDelete != null) + r.Remove(toDelete); + }); + + // Trigger the Replace action with a beatmap that is not in realm. + var staleSet = new BeatmapSetInfo + { + ID = detachedSet.ID, + OnlineID = detachedSet.OnlineID, + DateAdded = detachedSet.DateAdded, + DateSubmitted = detachedSet.DateSubmitted, + Status = detachedSet.Status, + Hash = detachedSet.Hash, + Protected = detachedSet.Protected, + }; + + var staleBeatmap = createBeatmap(staleSet, selectedBeatmap); + staleSet.Beatmaps.Add(staleBeatmap); + BeatmapSets.ReplaceRange(targetSetIndex, 1, [staleSet]); + }); + + WaitForFiltering(); + + AddAssert("deleted match never requested for selection", () => BeatmapRequestedSelections, () => Is.Empty); + } + private void assertDidFilter(int count = 1) => AddAssert("did filter", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count + count)); private void assertDidNotFilter() => AddAssert("did not filter", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count)); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2cf8c7d2d041..f2dcb1e93f3c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -259,20 +259,26 @@ bool attemptSelection(CarouselItem item) newSetBeatmaps.FirstOrDefault(b => b.OnlineID > 0 && b.OnlineID == beatmap.OnlineID) ?? newSetBeatmaps.FirstOrDefault(b => b.DifficultyName == beatmap.DifficultyName && b.Ruleset.Equals(beatmap.Ruleset)); - // The matching beatmap may have been deleted or invalidated in some way since this event was fired. - // Let's make sure we have the most up-to-date realm state. - if (matchingNewBeatmap?.ID is Guid matchingID) - matchingNewBeatmap = realm.Run(r => r.FindWithRefresh(matchingID)?.Detach()); - if (matchingNewBeatmap != null) { // TODO: should this exist in song select instead of here? // we need to ensure the global beatmap is also updated alongside changes. if (CurrentBeatmap != null && beatmap.Equals(CurrentBeatmap)) - // we don't know in which group the matching new beatmap is, but that's fine - we can keep the previous one for now. - // we are about to modify `Items`, which - if required - will trigger a re-filter, - // which will pick a correct group - if one is present - via `HandleFilterCompleted()`. - RequestSelection(new GroupedBeatmap(CurrentGroupedBeatmap?.Group, matchingNewBeatmap)); + { + // The matching beatmap may have been deleted or invalidated in some way since this event was fired. + // Let's make sure we have the most up-to-date realm state of the current beatmap. + var refreshedNewBeatmap = realm.Run(r => r.FindWithRefresh(matchingNewBeatmap.ID)?.Detach()); + + if (refreshedNewBeatmap != null) + { + matchingNewBeatmap = refreshedNewBeatmap; + + // we don't know in which group the matching new beatmap is, but that's fine - we can keep the previous one for now. + // we are about to modify `Items`, which - if required - will trigger a re-filter, + // which will pick a correct group - if one is present - via `HandleFilterCompleted()`. + RequestSelection(new GroupedBeatmap(CurrentGroupedBeatmap?.Group, matchingNewBeatmap)); + } + } Items.ReplaceRange(previousIndex, 1, [matchingNewBeatmap]); newSetBeatmaps.Remove(matchingNewBeatmap);