From 0b93f3c88f2f1d2a6ae7b3e2300d5b77f8606cfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 21:58:16 +0900 Subject: [PATCH] Add `` dictionary to speed up update operations in carousel --- osu.Game/Screens/Select/BeatmapCarousel.cs | 99 ++++++++++++++-------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0ee59f7f04..458a987130 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select protected readonly CarouselScrollContainer Scroll; - private IEnumerable beatmapSets => root.Children.OfType(); + private IEnumerable beatmapSets => root.BeatmapSetsByID.Values; // todo: only used for testing, maybe remove. private bool loadedTestBeatmaps; @@ -117,6 +117,7 @@ namespace osu.Game.Screens.Select newRoot.AddChildren(beatmapSets.Select(s => createCarouselSet(s.Detach())).Where(g => g != null)); root = newRoot; + if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) selectedBeatmapSet = null; @@ -209,7 +210,7 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - RemoveBeatmapSet(sender[i].Detach()); + removeBeatmapSet(sender[i].ID); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -223,24 +224,20 @@ namespace osu.Game.Screens.Select // During initial population, we must manually account for the fact that our original query was done on an async thread. // Since then, there may have been imports or deletions. // Here we manually catch up on any changes. - var populatedSets = new HashSet(); - foreach (var s in beatmapSets) - populatedSets.Add(s.BeatmapSet.ID); - var realmSets = new HashSet(); foreach (var s in sender) realmSets.Add(s.ID); - foreach (var s in realmSets) + foreach (var id in realmSets) { - if (!populatedSets.Contains(s)) - UpdateBeatmapSet(realmFactory.Context.Find(s)); + if (!root.BeatmapSetsByID.ContainsKey(id)) + UpdateBeatmapSet(realmFactory.Context.Find(id).Detach()); } - foreach (var s in populatedSets) + foreach (var id in root.BeatmapSetsByID.Keys) { - if (!realmSets.Contains(s)) - RemoveBeatmapSet(realmFactory.Context.Find(s)); + if (!realmSets.Contains(id)) + removeBeatmapSet(id); } signalBeatmapsLoaded(); @@ -261,16 +258,30 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - UpdateBeatmapSet(sender[i].BeatmapSet?.Detach()); + { + var beatmapInfo = sender[i]; + var beatmapSet = beatmapInfo.BeatmapSet; + + Debug.Assert(beatmapSet != null); + + // Only require to action here if the beatmap is missing. + // This avoids processing these events unnecessarily when new beatmaps are imported, for example. + if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSet) + && existingSet.BeatmapSet.Beatmaps.All(b => b.ID != beatmapInfo.ID)) + { + UpdateBeatmapSet(beatmapSet.Detach()); + } + } } private IRealmCollection getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => - { - var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet)); + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => + removeBeatmapSet(beatmapSet.ID); - if (existingSet == null) + private void removeBeatmapSet(Guid beatmapSetID) => Schedule(() => + { + if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSet)) return; root.RemoveChild(existingSet); @@ -281,33 +292,27 @@ namespace osu.Game.Screens.Select { Guid? previouslySelectedID = null; - CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet)); - // If the selected beatmap is about to be removed, store its ID so it can be re-selected if required - if (existingSet?.State?.Value == CarouselItemState.Selected) + if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID) previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; var newSet = createCarouselSet(beatmapSet); - if (existingSet != null) - root.RemoveChild(existingSet); + root.RemoveChild(beatmapSet.ID); - if (newSet == null) + if (newSet != null) { - itemsCache.Invalidate(); - return; + root.AddChild(newSet); + + // only reset scroll position if already near the scroll target. + // without this, during a large beatmap import it is impossible to navigate the carousel. + applyActiveCriteria(false, alwaysResetScrollPosition: false); + + // check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); } - root.AddChild(newSet); - - // only reset scroll position if already near the scroll target. - // without this, during a large beatmap import it is impossible to navigate the carousel. - applyActiveCriteria(false, alwaysResetScrollPosition: false); - - // check if we can/need to maintain our current selection. - if (previouslySelectedID != null) - select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); - itemsCache.Invalidate(); Schedule(() => BeatmapSetsChanged?.Invoke()); }); @@ -911,6 +916,8 @@ namespace osu.Game.Screens.Select { private readonly BeatmapCarousel carousel; + public readonly Dictionary BeatmapSetsByID = new Dictionary(); + public CarouselRoot(BeatmapCarousel carousel) { // root should always remain selected. if not, PerformSelection will not be called. @@ -920,6 +927,28 @@ namespace osu.Game.Screens.Select this.carousel = carousel; } + public override void AddChild(CarouselItem i) + { + CarouselBeatmapSet set = (CarouselBeatmapSet)i; + BeatmapSetsByID[set.BeatmapSet.ID] = set; + + base.AddChild(i); + } + + public void RemoveChild(Guid beatmapSetID) + { + if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet)) + RemoveChild(carouselBeatmapSet); + } + + public override void RemoveChild(CarouselItem i) + { + CarouselBeatmapSet set = (CarouselBeatmapSet)i; + BeatmapSetsByID.Remove(set.BeatmapSet.ID); + + base.RemoveChild(i); + } + protected override void PerformSelection() { if (LastSelected == null || LastSelected.Filtered.Value)