1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-03 06:09:54 +08:00

Fix DifficultySpectrumDisplay churning drawables

Was causing so much GC that song select (v2) was grinding to a halt.
This commit is contained in:
Dean Herbert
2025-03-19 17:14:32 +09:00
Unverified
parent cfb14fbca6
commit 63fcde5538
2 changed files with 134 additions and 58 deletions
@@ -1,12 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Framework.Testing;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.API.Requests.Responses;
using osuTK;
@@ -15,14 +13,18 @@ namespace osu.Game.Tests.Visual.Beatmaps
{
public partial class TestSceneDifficultySpectrumDisplay : OsuTestScene
{
private static APIBeatmapSet createBeatmapSetWith(params (int rulesetId, double stars)[] difficulties) => new APIBeatmapSet
private DifficultySpectrumDisplay display = null!;
[SetUpSteps]
public void SetUpSteps()
{
Beatmaps = difficulties.Select(difficulty => new APIBeatmap
AddStep("create spectrum display", () => Child = display = new DifficultySpectrumDisplay
{
RulesetID = difficulty.rulesetId,
StarRating = difficulty.stars
}).ToArray()
};
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(3)
});
}
[Test]
public void TestSingleRuleset()
@@ -32,7 +34,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
(rulesetId: 0, stars: 3.2),
(rulesetId: 0, stars: 5.6));
createDisplay(beatmapSet);
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
}
[Test]
@@ -45,7 +47,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
(rulesetId: 1, stars: 4.3),
(rulesetId: 0, stars: 5.6));
createDisplay(beatmapSet);
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
}
[Test]
@@ -59,29 +61,30 @@ namespace osu.Game.Tests.Visual.Beatmaps
(rulesetId: 0, stars: 5.6),
(rulesetId: 15, stars: 7.8));
createDisplay(beatmapSet);
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
}
[Test]
public void TestMaximumUncollapsed()
{
var beatmapSet = createBeatmapSetWith(Enumerable.Range(0, 12).Select(i => (rulesetId: i % 4, stars: 2.5 + i * 0.25)).ToArray());
createDisplay(beatmapSet);
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
}
[Test]
public void TestMinimumCollapsed()
{
var beatmapSet = createBeatmapSetWith(Enumerable.Range(0, 13).Select(i => (rulesetId: i % 4, stars: 2.5 + i * 0.25)).ToArray());
createDisplay(beatmapSet);
AddStep("set beatmap to display", () => display.BeatmapSet = beatmapSet);
}
private void createDisplay(IBeatmapSetInfo beatmapSetInfo) => AddStep("create spectrum display", () => Child = new DifficultySpectrumDisplay
private static APIBeatmapSet createBeatmapSetWith(params (int rulesetId, double stars)[] difficulties) => new APIBeatmapSet
{
BeatmapSet = beatmapSetInfo,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(3)
});
Beatmaps = difficulties.Select(difficulty => new APIBeatmap
{
RulesetID = difficulty.rulesetId,
StarRating = difficulty.stars
}).ToArray()
};
}
}
@@ -34,6 +34,8 @@ namespace osu.Game.Beatmaps.Drawables
private FillFlowContainer<RulesetDifficultyGroup> flow = null!;
private const int max_difficulties_before_collapsing = 12;
[BackgroundDependencyLoader]
private void load()
{
@@ -55,31 +57,71 @@ namespace osu.Game.Beatmaps.Drawables
private void updateDisplay()
{
flow.Clear();
foreach (var group in flow)
group.Alpha = 0;
if (beatmapSet == null)
{
foreach (var group in flow)
group.Beatmaps = [];
return;
}
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
bool collapsed = beatmapSet.Beatmaps.Count() > max_difficulties_before_collapsing;
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset).OrderBy(group => group.Key))
{
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key.OnlineID, rulesetGrouping, collapsed));
int rulesetId = rulesetGrouping.Key.OnlineID;
var group = flow.SingleOrDefault(rg => rg.RulesetId == rulesetId);
if (group == null)
{
group = new RulesetDifficultyGroup(rulesetId);
flow.Add(group);
flow.SetLayoutPosition(group, rulesetId);
}
group.Alpha = 1;
group.Beatmaps = rulesetGrouping;
group.Collapsed = collapsed;
}
}
private partial class RulesetDifficultyGroup : FillFlowContainer
{
private readonly int rulesetId;
private readonly IEnumerable<IBeatmapInfo> beatmapInfos;
private readonly bool collapsed;
public readonly int RulesetId;
public RulesetDifficultyGroup(int rulesetId, IEnumerable<IBeatmapInfo> beatmapInfos, bool collapsed)
private IEnumerable<IBeatmapInfo> beatmaps = [];
public IEnumerable<IBeatmapInfo> Beatmaps
{
this.rulesetId = rulesetId;
this.beatmapInfos = beatmapInfos;
this.collapsed = collapsed;
get => beatmaps;
set
{
beatmaps = value;
updateDisplay();
}
}
private bool collapsed;
public bool Collapsed
{
get => collapsed;
set
{
collapsed = value;
updateDisplay();
}
}
private OsuSpriteText countText = null!;
public RulesetDifficultyGroup(int rulesetId)
{
RulesetId = rulesetId;
}
[BackgroundDependencyLoader]
@@ -89,53 +131,84 @@ namespace osu.Game.Beatmaps.Drawables
Spacing = new Vector2(1, 0);
Direction = FillDirection.Horizontal;
var icon = rulesets.GetRuleset(rulesetId)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle };
var icon = rulesets.GetRuleset(RulesetId)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle };
Add(icon.With(i =>
{
i.Size = new Vector2(14);
i.Anchor = i.Origin = Anchor.Centre;
}));
if (!collapsed)
for (int i = 0; i < max_difficulties_before_collapsing; i++)
Add(new DifficultyDot());
Add(countText = new OsuSpriteText
{
foreach (var beatmapInfo in beatmapInfos.OrderBy(bi => bi.StarRating))
Add(new DifficultyDot(beatmapInfo.StarRating));
}
else
Font = OsuFont.Default.With(size: 12),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding { Bottom = 1 }
});
}
protected override void LoadComplete()
{
base.LoadComplete();
updateDisplay();
}
private void updateDisplay()
{
countText.Alpha = collapsed ? 1 : 0;
countText.Text = beatmaps.Count().ToLocalisableString(@"N0");
var orderedBeatmaps = beatmaps.OrderBy(bi => bi.StarRating).ToArray();
var dots = this.OfType<DifficultyDot>().ToArray();
for (int i = 0; i < max_difficulties_before_collapsing; i++)
{
Add(new OsuSpriteText
var dot = dots[i];
if (collapsed || i >= orderedBeatmaps.Length)
{
Text = beatmapInfos.Count().ToLocalisableString(@"N0"),
Font = OsuFont.Default.With(size: 12),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding { Bottom = 1 }
});
dot.Alpha = 0;
continue;
}
dot.Alpha = 1;
dot.StarDifficulty = orderedBeatmaps[i].StarRating;
}
}
}
private partial class DifficultyDot : CircularContainer
private partial class DifficultyDot : Circle
{
private readonly double starDifficulty;
private double starDifficulty;
public DifficultyDot(double starDifficulty)
public double StarDifficulty
{
this.starDifficulty = starDifficulty;
Size = new Vector2(5, 10);
get => starDifficulty;
set
{
starDifficulty = value;
updateColour();
}
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Anchor = Origin = Anchor.Centre;
Masking = true;
[Resolved]
private OsuColour colours { get; set; } = null!;
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.ForStarDifficulty(starDifficulty)
};
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(5, 10);
Anchor = Origin = Anchor.Centre;
updateColour();
}
private void updateColour()
{
Colour = colours.ForStarDifficulty(starDifficulty);
}
}
}