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);