From a8ce2c5edf15f5af96cb8e14183e89757c650ae9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 21:14:10 +0900 Subject: [PATCH 01/11] Detach before sending `BeatmapSetInfo` to any handling method --- osu.Game/Screens/Select/BeatmapCarousel.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 75ad0511e6..0ee59f7f04 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Select { CarouselRoot newRoot = new CarouselRoot(this); - newRoot.AddChildren(beatmapSets.Select(createCarouselSet).Where(g => g != null)); + newRoot.AddChildren(beatmapSets.Select(s => createCarouselSet(s.Detach())).Where(g => g != null)); root = newRoot; if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) @@ -209,7 +209,7 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - RemoveBeatmapSet(sender[i]); + RemoveBeatmapSet(sender[i].Detach()); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -248,10 +248,10 @@ namespace osu.Game.Screens.Select } foreach (int i in changes.NewModifiedIndices) - UpdateBeatmapSet(sender[i]); + UpdateBeatmapSet(sender[i].Detach()); foreach (int i in changes.InsertedIndices) - UpdateBeatmapSet(sender[i]); + UpdateBeatmapSet(sender[i].Detach()); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Select return; foreach (int i in changes.InsertedIndices) - UpdateBeatmapSet(sender[i].BeatmapSet); + UpdateBeatmapSet(sender[i].BeatmapSet?.Detach()); } private IRealmCollection getBeatmapSets(Realm realm) => realm.All().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection(); @@ -711,8 +711,6 @@ namespace osu.Game.Screens.Select private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) { - beatmapSet = beatmapSet.Detach(); - // This can be moved to the realm query if required using: // .Filter("DeletePending == false && Protected == false && ANY Beatmaps.Hidden == false") // From 9a864267d2054cb48fe06db37ccbdd9e4b8f26fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 21:57:16 +0900 Subject: [PATCH 02/11] Fix `CarouselGroupEagerSelect` not invoking subclassed `AddChild` from `AddChildren` calls --- .../Select/Carousel/CarouselGroupEagerSelect.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 9e8aad4b6f..aac0e4ed82 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -55,10 +55,16 @@ namespace osu.Game.Screens.Select.Carousel updateSelectedIndex(); } + private bool addingChildren; + public void AddChildren(IEnumerable items) { + addingChildren = true; + foreach (var i in items) - base.AddChild(i); + AddChild(i); + + addingChildren = false; attemptSelection(); } @@ -66,7 +72,8 @@ namespace osu.Game.Screens.Select.Carousel public override void AddChild(CarouselItem i) { base.AddChild(i); - attemptSelection(); + if (!addingChildren) + attemptSelection(); } protected override void ChildItemStateChanged(CarouselItem item, CarouselItemState value) From 0b93f3c88f2f1d2a6ae7b3e2300d5b77f8606cfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 21:58:16 +0900 Subject: [PATCH 03/11] 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) From 80f3a67876adcad87ba910e41e7bc82fd46cf820 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 Jan 2022 22:21:00 +0900 Subject: [PATCH 04/11] Use `for` instead of `foreach` to avoid enumerator overhead --- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 458a987130..a5704b3b2e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -225,8 +225,9 @@ namespace osu.Game.Screens.Select // Since then, there may have been imports or deletions. // Here we manually catch up on any changes. var realmSets = new HashSet(); - foreach (var s in sender) - realmSets.Add(s.ID); + + for (int i = 0; i < sender.Count; i++) + realmSets.Add(sender[i].ID); foreach (var id in realmSets) { From ba31ddee017a950be2c8b50c4c9750ebeb575af6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 00:33:46 +0900 Subject: [PATCH 05/11] Revert `beatmapSets` reference to fix tests New version changed order. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a5704b3b2e..961dac9856 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.BeatmapSetsByID.Values; + private IEnumerable beatmapSets => root.Children.OfType(); // todo: only used for testing, maybe remove. private bool loadedTestBeatmaps; From 079b2dfc42ac22168dbc4f436653a648b51f9ad8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 00:01:34 +0900 Subject: [PATCH 06/11] Create backup of databases before opening contexts Attempt to avoid file IO issues. Closes #16531. --- osu.Game/Database/EFToRealmMigrator.cs | 30 ++++++++------------------ 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 0f726f8ee5..727815cc4d 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -26,8 +26,6 @@ namespace osu.Game.Database private readonly OsuConfigManager config; private readonly Storage storage; - private bool hasTakenBackup; - public EFToRealmMigrator(DatabaseContextFactory efContextFactory, RealmContextFactory realmContextFactory, OsuConfigManager config, Storage storage) { this.efContextFactory = efContextFactory; @@ -38,6 +36,8 @@ namespace osu.Game.Database public void Run() { + createBackup(); + using (var ef = efContextFactory.Get()) { migrateSettings(ef); @@ -77,8 +77,6 @@ namespace osu.Game.Database { Logger.Log($"Found {count} beatmaps in EF", LoggingTarget.Database); - ensureBackup(); - // only migrate data if the realm database is empty. // note that this cannot be written as: `realm.All().All(s => s.Protected)`, because realm does not support `.All()`. if (realm.All().Any(s => !s.Protected)) @@ -210,8 +208,6 @@ namespace osu.Game.Database { Logger.Log($"Found {count} scores in EF", LoggingTarget.Database); - ensureBackup(); - // only migrate data if the realm database is empty. if (realm.All().Any()) { @@ -291,8 +287,6 @@ namespace osu.Game.Database if (!existingSkins.Any()) return; - ensureBackup(); - var userSkinChoice = config.GetBindable(OsuSetting.Skin); int.TryParse(userSkinChoice.Value, out int userSkinInt); @@ -363,7 +357,6 @@ namespace osu.Game.Database return; Logger.Log("Beginning settings migration to realm", LoggingTarget.Database); - ensureBackup(); using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) @@ -400,21 +393,16 @@ namespace osu.Game.Database private string? getRulesetShortNameFromLegacyID(long rulesetId) => efContextFactory.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; - private void ensureBackup() + private void createBackup() { - if (!hasTakenBackup) - { - string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; + string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; - efContextFactory.CreateBackup($"client.{migration}.db"); - realmContextFactory.CreateBackup($"client.{migration}.realm"); + efContextFactory.CreateBackup($"client.{migration}.db"); + realmContextFactory.CreateBackup($"client.{migration}.realm"); - using (var source = storage.GetStream("collection.db")) - using (var destination = storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); - - hasTakenBackup = true; - } + using (var source = storage.GetStream("collection.db")) + using (var destination = storage.GetStream($"collection.{migration}.db", FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); } } } From 7aad2780b1fae24d9fd6213f0e44c519fc87cc89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 00:46:47 +0900 Subject: [PATCH 07/11] Add retry logic for realm backup creation --- osu.Game/Database/RealmContextFactory.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 31dbb0c6c4..ffadf8258d 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -367,9 +367,24 @@ namespace osu.Game.Database using (BlockAllOperations()) { Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database); - using (var source = storage.GetStream(Filename)) - using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); + + int attempts = 10; + + while (attempts-- > 0) + { + try + { + using (var source = storage.GetStream(Filename)) + using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); + return; + } + catch (IOException) + { + // file may be locked during use. + Thread.Sleep(500); + } + } } } From 45bf35c42532d4051166df6a8df955d41a8b7e49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 12:26:24 +0900 Subject: [PATCH 08/11] Avoid performing keyword filtering at song select unless keywords are specified --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index d54a3bb54e..6b198ab505 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating); - if (match) + if (match && criteria.SearchTerms.Length > 0) { string[] terms = BeatmapInfo.GetSearchableTerms(); From 5b24800b0e9c88af8747f9e33a3c14b97ca1f4d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 12:37:17 +0900 Subject: [PATCH 09/11] Avoid applying filter in `UpdateBeatmapSet` flow --- osu.Game/Screens/Select/BeatmapCarousel.cs | 12 +++++---- .../Screens/Select/Carousel/CarouselGroup.cs | 27 +++++++++++++++++-- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 75ad0511e6..6d3d7aa185 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -300,16 +300,18 @@ namespace osu.Game.Screens.Select 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()); + Schedule(() => + { + if (!Scroll.UserScrolling) + ScrollToSelected(true); + + BeatmapSetsChanged?.Invoke(); + }); }); /// diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index b85e868b89..7e4c5fad72 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; namespace osu.Game.Screens.Select.Carousel { @@ -36,7 +37,21 @@ namespace osu.Game.Screens.Select.Carousel { i.State.ValueChanged += state => ChildItemStateChanged(i, state.NewValue); i.ChildID = ++currentChildID; - InternalChildren.Add(i); + + if (lastCriteria != null) + { + i.Filter(lastCriteria); + + int index = InternalChildren.BinarySearch(i, criteriaComparer); + if (index < 0) index = ~index; // BinarySearch hacks multiple return values with 2's complement. + + InternalChildren.Insert(index, i); + } + else + { + // criteria may be null for initial population. the filtering will be applied post-add. + InternalChildren.Add(i); + } } public CarouselGroup(List items = null) @@ -62,14 +77,22 @@ namespace osu.Game.Screens.Select.Carousel }; } + private Comparer criteriaComparer; + + [CanBeNull] + private FilterCriteria lastCriteria; + public override void Filter(FilterCriteria criteria) { base.Filter(criteria); InternalChildren.ForEach(c => c.Filter(criteria)); + // IEnumerable.OrderBy() is used instead of List.Sort() to ensure sorting stability - var criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); + criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); InternalChildren = InternalChildren.OrderBy(c => c, criteriaComparer).ToList(); + + lastCriteria = criteria; } protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value) From bed7b69464ed1b6971ae84ee81885d6ce90c5fed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 13:09:03 +0900 Subject: [PATCH 10/11] Apply NRT to `CarouselGroup` --- .../Screens/Select/Carousel/CarouselGroup.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 7e4c5fad72..6ebe314072 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -3,7 +3,8 @@ using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; + +#nullable enable namespace osu.Game.Screens.Select.Carousel { @@ -12,7 +13,7 @@ namespace osu.Game.Screens.Select.Carousel /// public class CarouselGroup : CarouselItem { - public override DrawableCarouselItem CreateDrawableRepresentation() => null; + public override DrawableCarouselItem? CreateDrawableRepresentation() => null; public IReadOnlyList Children => InternalChildren; @@ -24,6 +25,10 @@ namespace osu.Game.Screens.Select.Carousel /// private ulong currentChildID; + private Comparer? criteriaComparer; + + private FilterCriteria? lastCriteria; + public virtual void RemoveChild(CarouselItem i) { InternalChildren.Remove(i); @@ -54,7 +59,7 @@ namespace osu.Game.Screens.Select.Carousel } } - public CarouselGroup(List items = null) + public CarouselGroup(List? items = null) { if (items != null) InternalChildren = items; @@ -77,11 +82,6 @@ namespace osu.Game.Screens.Select.Carousel }; } - private Comparer criteriaComparer; - - [CanBeNull] - private FilterCriteria lastCriteria; - public override void Filter(FilterCriteria criteria) { base.Filter(criteria); From 3bcdce128c5927a90f718b0ef54d76678272648a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 Jan 2022 15:29:21 +0900 Subject: [PATCH 11/11] Use dictionary add for safety --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3cd9253eff..e8171d1512 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -934,7 +934,7 @@ namespace osu.Game.Screens.Select public override void AddChild(CarouselItem i) { CarouselBeatmapSet set = (CarouselBeatmapSet)i; - BeatmapSetsByID[set.BeatmapSet.ID] = set; + BeatmapSetsByID.Add(set.BeatmapSet.ID, set); base.AddChild(i); }