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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user