From 421f245c3102bb2ab3719b7ff4717b401a0db11a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 14:31:59 +0900 Subject: [PATCH 1/3] Fix per-frame allocations in `BeatmapCarousel` --- .../SongSelect/TestSceneBeatmapCarousel.cs | 4 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 11 +++++++---- .../Carousel/DrawableCarouselBeatmapSet.cs | 18 ++++++++---------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index ec072a3dd2..218a67e818 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -1405,9 +1405,9 @@ namespace osu.Game.Tests.Visual.SongSelect yield return item; - if (item is DrawableCarouselBeatmapSet set) + if (item is DrawableCarouselBeatmapSet set && set.Beatmaps?.IsLoaded == true) { - foreach (var difficulty in set.DrawableBeatmaps) + foreach (var difficulty in set.Beatmaps) yield return difficulty; } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 87cea45e87..f7e0eae4a5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -857,8 +857,9 @@ namespace osu.Game.Screens.Select // Add those items within the previously found index range that should be displayed. foreach (var item in toDisplay) { - var panel = setPool.Get(p => p.Item = item); + var panel = setPool.Get(); + panel.Item = item; panel.Y = item.CarouselYPosition; Scroll.Add(panel); @@ -898,10 +899,12 @@ namespace osu.Game.Screens.Select Scroll.ChangeChildDepth(item, hasPassedSelection ? -item.Item.CarouselYPosition : item.Item.CarouselYPosition); } - if (item is DrawableCarouselBeatmapSet set) + if (item is DrawableCarouselBeatmapSet set && set.Beatmaps?.IsLoaded == true) { - foreach (var diff in set.DrawableBeatmaps) + foreach (var diff in set.Beatmaps) + { updateItem(diff, item); + } } } } @@ -1101,7 +1104,7 @@ namespace osu.Game.Screens.Select } /// - /// Update a item's x position and multiplicative alpha based on its y position and + /// Update an item's x position and multiplicative alpha based on its y position and /// the current scroll position. /// /// The item to be updated. diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 1cd8b065fc..9a01b46216 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -51,9 +51,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private IBindable ruleset { get; set; } = null!; - public IEnumerable DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Enumerable.Empty() : beatmapContainer.AliveChildren; - - private Container? beatmapContainer; + public Container? Beatmaps; private BeatmapSetInfo beatmapSet = null!; @@ -126,7 +124,7 @@ namespace osu.Game.Screens.Select.Carousel Content.Clear(); Header.Clear(); - beatmapContainer = null; + Beatmaps = null; beatmapsLoadTask = null; if (Item == null) @@ -164,7 +162,7 @@ namespace osu.Game.Screens.Select.Carousel // if we are already displaying all the correct beatmaps, only run animation updates. // note that the displayed beatmaps may change due to the applied filter. // a future optimisation could add/remove only changed difficulties rather than reinitialise. - if (beatmapContainer != null && visibleBeatmaps.Length == beatmapContainer.Count && visibleBeatmaps.All(b => beatmapContainer.Any(c => c.Item == b))) + if (Beatmaps != null && visibleBeatmaps.Length == Beatmaps.Count && visibleBeatmaps.All(b => Beatmaps.Any(c => c.Item == b))) { updateBeatmapYPositions(); } @@ -173,17 +171,17 @@ namespace osu.Game.Screens.Select.Carousel // on selection we show our child beatmaps. // for now this is a simple drawable construction each selection. // can be improved in the future. - beatmapContainer = new Container + Beatmaps = new Container { X = 100, RelativeSizeAxes = Axes.Both, ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()!) }; - beatmapsLoadTask = LoadComponentAsync(beatmapContainer, loaded => + beatmapsLoadTask = LoadComponentAsync(Beatmaps, loaded => { // make sure the pooled target hasn't changed. - if (beatmapContainer != loaded) + if (Beatmaps != loaded) return; Content.Child = loaded; @@ -244,7 +242,7 @@ namespace osu.Game.Screens.Select.Carousel private void updateBeatmapYPositions() { - if (beatmapContainer == null) + if (Beatmaps == null) return; if (beatmapsLoadTask == null || !beatmapsLoadTask.IsCompleted) @@ -254,7 +252,7 @@ namespace osu.Game.Screens.Select.Carousel bool isSelected = Item?.State.Value == CarouselItemState.Selected; - foreach (var panel in beatmapContainer) + foreach (var panel in Beatmaps) { Debug.Assert(panel.Item != null); From 97a51af5a06351d64944951963d82dba4df65c16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 14:52:52 +0900 Subject: [PATCH 2/3] Fix one more unnecessary enumerator allocation --- .../Overlays/NotificationOverlayToastTray.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index d2899f29b8..df07b4f138 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -153,8 +153,22 @@ namespace osu.Game.Overlays { base.Update(); - float height = toastFlow.Count > 0 ? toastFlow.DrawHeight + 120 : 0; - float alpha = toastFlow.Count > 0 ? MathHelper.Clamp(toastFlow.DrawHeight / 41, 0, 1) * toastFlow.Children.Max(n => n.Alpha) : 0; + float height = 0; + float alpha = 0; + + if (toastFlow.Count > 0) + { + float maxNotificationAlpha = 0; + + foreach (var t in toastFlow) + { + if (t.Alpha > maxNotificationAlpha) + maxNotificationAlpha = t.Alpha; + } + + height = toastFlow.DrawHeight + 120; + alpha = MathHelper.Clamp(toastFlow.DrawHeight / 41, 0, 1) * maxNotificationAlpha; + } toastContentBackground.Height = (float)Interpolation.DampContinuously(toastContentBackground.Height, height, 10, Clock.ElapsedFrameTime); toastContentBackground.Alpha = (float)Interpolation.DampContinuously(toastContentBackground.Alpha, alpha, 10, Clock.ElapsedFrameTime); From dfe11dc68a4f381a3d6ec78d30462929dd3efe5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Sep 2024 15:25:36 +0900 Subject: [PATCH 3/3] Use `for` with exposed `IReadOnlyList` rather than making internal container public --- .../SongSelect/TestSceneBeatmapCarousel.cs | 4 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 +++----- .../Carousel/DrawableCarouselBeatmapSet.cs | 18 ++++++++++-------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 218a67e818..ec072a3dd2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -1405,9 +1405,9 @@ namespace osu.Game.Tests.Visual.SongSelect yield return item; - if (item is DrawableCarouselBeatmapSet set && set.Beatmaps?.IsLoaded == true) + if (item is DrawableCarouselBeatmapSet set) { - foreach (var difficulty in set.Beatmaps) + foreach (var difficulty in set.DrawableBeatmaps) yield return difficulty; } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f7e0eae4a5..a6a6a2f585 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -899,12 +899,10 @@ namespace osu.Game.Screens.Select Scroll.ChangeChildDepth(item, hasPassedSelection ? -item.Item.CarouselYPosition : item.Item.CarouselYPosition); } - if (item is DrawableCarouselBeatmapSet set && set.Beatmaps?.IsLoaded == true) + if (item is DrawableCarouselBeatmapSet set) { - foreach (var diff in set.Beatmaps) - { - updateItem(diff, item); - } + for (int i = 0; i < set.DrawableBeatmaps.Count; i++) + updateItem(set.DrawableBeatmaps[i], item); } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 9a01b46216..eba40994e2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -51,7 +51,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private IBindable ruleset { get; set; } = null!; - public Container? Beatmaps; + public IReadOnlyList DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Array.Empty() : beatmapContainer; + + private Container? beatmapContainer; private BeatmapSetInfo beatmapSet = null!; @@ -124,7 +126,7 @@ namespace osu.Game.Screens.Select.Carousel Content.Clear(); Header.Clear(); - Beatmaps = null; + beatmapContainer = null; beatmapsLoadTask = null; if (Item == null) @@ -162,7 +164,7 @@ namespace osu.Game.Screens.Select.Carousel // if we are already displaying all the correct beatmaps, only run animation updates. // note that the displayed beatmaps may change due to the applied filter. // a future optimisation could add/remove only changed difficulties rather than reinitialise. - if (Beatmaps != null && visibleBeatmaps.Length == Beatmaps.Count && visibleBeatmaps.All(b => Beatmaps.Any(c => c.Item == b))) + if (beatmapContainer != null && visibleBeatmaps.Length == beatmapContainer.Count && visibleBeatmaps.All(b => beatmapContainer.Any(c => c.Item == b))) { updateBeatmapYPositions(); } @@ -171,17 +173,17 @@ namespace osu.Game.Screens.Select.Carousel // on selection we show our child beatmaps. // for now this is a simple drawable construction each selection. // can be improved in the future. - Beatmaps = new Container + beatmapContainer = new Container { X = 100, RelativeSizeAxes = Axes.Both, ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation()!) }; - beatmapsLoadTask = LoadComponentAsync(Beatmaps, loaded => + beatmapsLoadTask = LoadComponentAsync(beatmapContainer, loaded => { // make sure the pooled target hasn't changed. - if (Beatmaps != loaded) + if (beatmapContainer != loaded) return; Content.Child = loaded; @@ -242,7 +244,7 @@ namespace osu.Game.Screens.Select.Carousel private void updateBeatmapYPositions() { - if (Beatmaps == null) + if (beatmapContainer == null) return; if (beatmapsLoadTask == null || !beatmapsLoadTask.IsCompleted) @@ -252,7 +254,7 @@ namespace osu.Game.Screens.Select.Carousel bool isSelected = Item?.State.Value == CarouselItemState.Selected; - foreach (var panel in Beatmaps) + foreach (var panel in beatmapContainer) { Debug.Assert(panel.Item != null);