1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-25 17:49:57 +08:00

Merge pull request #33002 from frenzibyte/song-select-async-beatmap-load

Fix song select title wedge not accessing beatmap asynchronously
This commit is contained in:
Dean Herbert
2025-05-02 19:57:49 +09:00
committed by GitHub
Unverified
3 changed files with 146 additions and 42 deletions
@@ -2,11 +2,16 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -16,9 +21,12 @@ using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.SelectV2;
using osu.Game.Skinning;
using osu.Game.Tests.Visual.SongSelect;
namespace osu.Game.Tests.Visual.SongSelectV2
@@ -193,6 +201,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2
checkDisplayedBPM(expectedDisplay);
}
[Test]
[Explicit]
public void TestPerformanceWithLongBeatmap()
{
AddStep("select heavy beatmap", () => Beatmap.Value = new HeavyWorkingBeatmap(Audio));
foreach (var rulesetInfo in rulesets.AvailableRulesets)
setRuleset(rulesetInfo);
}
private void setRuleset(RulesetInfo rulesetInfo)
{
AddStep("set ruleset", () => Ruleset.Value = rulesetInfo);
@@ -238,5 +256,49 @@ namespace osu.Game.Tests.Visual.SongSelectV2
working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now;
return (working, onlineSet);
}
private class TestHitObject : ConvertHitObject;
private class HeavyWorkingBeatmap : WorkingBeatmap
{
private static readonly BeatmapInfo beatmap_info = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Author = { Username = "osuAuthor" },
Artist = "osuArtist",
Source = "osuSource",
Title = "osuTitle"
},
Ruleset = new OsuRuleset().RulesetInfo,
StarRating = 6,
DifficultyName = "osuVersion",
Difficulty = new BeatmapDifficulty()
};
public HeavyWorkingBeatmap(AudioManager audioManager)
: base(beatmap_info, audioManager)
{
}
protected override IBeatmap GetBeatmap()
{
List<HitObject> objects = new List<HitObject>();
for (int i = 0; i < 200_000; i++)
objects.Add(new TestHitObject { StartTime = i * 1000 });
return new Beatmap
{
BeatmapInfo = beatmap_info,
HitObjects = objects
};
}
public override Texture? GetBackground() => null;
public override Stream? GetStream(string storagePath) => null;
protected override Track? GetBeatmapTrack() => null;
protected internal override ISkin? GetSkin() => null;
}
}
}
+39 -19
View File
@@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
@@ -33,7 +35,7 @@ namespace osu.Game.Screens.SelectV2
private const float corner_radius = 10;
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
private IBindable<WorkingBeatmap> working { get; set; } = null!;
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
@@ -184,7 +186,7 @@ namespace osu.Game.Screens.SelectV2
{
base.LoadComplete();
beatmap.BindValueChanged(_ => updateDisplay());
working.BindValueChanged(_ => updateDisplay());
ruleset.BindValueChanged(_ => updateDisplay());
mods.BindValueChanged(m =>
@@ -224,9 +226,9 @@ namespace osu.Game.Screens.SelectV2
private void updateDisplay()
{
var metadata = beatmap.Value.Metadata;
var beatmapInfo = beatmap.Value.BeatmapInfo;
var beatmapSetInfo = beatmap.Value.BeatmapSetInfo;
var metadata = working.Value.Metadata;
var beatmapInfo = working.Value.BeatmapInfo;
var beatmapSetInfo = working.Value.BeatmapSetInfo;
statusPill.Status = beatmapInfo.Status;
@@ -246,30 +248,48 @@ namespace osu.Game.Screens.SelectV2
updateOnlineDisplay();
}
private CancellationTokenSource? lengthBpmCancellationSource;
private void updateLengthAndBpmStatistics()
{
var beatmapInfo = beatmap.Value.BeatmapInfo;
lengthBpmCancellationSource?.Cancel();
lengthBpmCancellationSource = new CancellationTokenSource();
double rate = ModUtils.CalculateRateWithMods(mods.Value);
var token = lengthBpmCancellationSource.Token;
int bpmMax = FormatUtils.RoundBPM(beatmap.Value.Beatmap.ControlPointInfo.BPMMaximum, rate);
int bpmMin = FormatUtils.RoundBPM(beatmap.Value.Beatmap.ControlPointInfo.BPMMinimum, rate);
int mostCommonBPM = FormatUtils.RoundBPM(60000 / beatmap.Value.Beatmap.GetMostCommonBeatLength(), rate);
Task.Run(() =>
{
var beatmapInfo = working.Value.BeatmapInfo;
// This can take time as it is a synchronous task.
var beatmap = working.Value.Beatmap;
double drainLength = Math.Round(beatmap.Value.Beatmap.CalculateDrainLength() / rate);
double hitLength = Math.Round(beatmapInfo.Length / rate);
double rate = ModUtils.CalculateRateWithMods(mods.Value);
lengthStatistic.Text = hitLength.ToFormattedDuration();
lengthStatistic.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(drainLength.ToFormattedDuration());
int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate);
int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate);
int mostCommonBPM = FormatUtils.RoundBPM(60000 / beatmap.GetMostCommonBeatLength(), rate);
bpmStatistic.Text = bpmMin == bpmMax
? $"{bpmMin}"
: $"{bpmMin}-{bpmMax} (mostly {mostCommonBPM})";
double drainLength = Math.Round(beatmap.CalculateDrainLength() / rate);
double hitLength = Math.Round(beatmapInfo.Length / rate);
Schedule(() =>
{
if (token.IsCancellationRequested)
return;
lengthStatistic.Text = hitLength.ToFormattedDuration();
lengthStatistic.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(drainLength.ToFormattedDuration());
bpmStatistic.Text = bpmMin == bpmMax
? $"{bpmMin}"
: $"{bpmMin}-{bpmMax} (mostly {mostCommonBPM})";
});
}, token);
}
private void refetchBeatmapSet()
{
var beatmapSetInfo = beatmap.Value.BeatmapSetInfo;
var beatmapSetInfo = working.Value.BeatmapSetInfo;
currentRequest?.Cancel();
currentRequest = null;
@@ -305,7 +325,7 @@ namespace osu.Game.Screens.SelectV2
else
{
var onlineBeatmapSet = currentOnlineBeatmapSet;
var onlineBeatmap = currentOnlineBeatmapSet.Beatmaps.SingleOrDefault(b => b.OnlineID == beatmap.Value.BeatmapInfo.OnlineID);
var onlineBeatmap = currentOnlineBeatmapSet.Beatmaps.SingleOrDefault(b => b.OnlineID == working.Value.BeatmapInfo.OnlineID);
playCount.Value = new StatisticPlayCount.Data(onlineBeatmap?.PlayCount ?? -1, onlineBeatmap?.UserPlayCount ?? -1);
favouritesStatistic.Text = onlineBeatmapSet.FavouriteCount.ToLocalisableString(@"N0");
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@@ -241,8 +242,6 @@ namespace osu.Game.Screens.SelectV2
cancellationSource?.Cancel();
cancellationSource = new CancellationTokenSource();
computeStarDifficulty(cancellationSource.Token);
if (beatmap.IsDefault)
{
ratingAndNameContainer.FadeOut(300, Easing.OutQuint);
@@ -254,17 +253,55 @@ namespace osu.Game.Screens.SelectV2
difficultyText.Text = beatmap.Value.BeatmapInfo.DifficultyName;
mapperLink.Action = () => linkHandler?.HandleLink(new LinkDetails(LinkAction.OpenUserProfile, beatmap.Value.Metadata.Author));
mapperText.Text = beatmap.Value.Metadata.Author.Username;
var playableBeatmap = beatmap.Value.GetPlayableBeatmap(ruleset.Value);
countStatisticsDisplay.Statistics = playableBeatmap.GetStatistics()
.Select(s => new StatisticDifficulty.Data(s.Name, s.BarDisplayLength ?? 0, s.BarDisplayLength ?? 0, 1, s.Content))
.ToList();
}
updateStarDifficulty(cancellationSource.Token);
updateCountStatistics(cancellationSource.Token);
updateDifficultyStatistics();
}
private void updateStarDifficulty(CancellationToken cancellationToken)
{
difficultyCache.GetDifficultyAsync(beatmap.Value.BeatmapInfo, ruleset.Value, mods.Value, cancellationToken)
.ContinueWith(task =>
{
Schedule(() =>
{
if (cancellationToken.IsCancellationRequested)
return;
starRatingDisplay.Current.Value = task.GetResultSafely() ?? default;
});
}, cancellationToken);
}
private void updateCountStatistics(CancellationToken cancellationToken)
{
if (beatmap.IsDefault)
{
countStatisticsDisplay.Statistics = Array.Empty<StatisticDifficulty.Data>();
return;
}
Task.Run(() =>
{
// This can take time as it is a synchronous task.
// TODO: We're calling `GetPlayableBeatmap` multiple times every map load at song select.
var playableBeatmap = beatmap.Value.GetPlayableBeatmap(ruleset.Value);
var statistics = playableBeatmap.GetStatistics()
.Select(s => new StatisticDifficulty.Data(s.Name, s.BarDisplayLength ?? 0, s.BarDisplayLength ?? 0, 1, s.Content))
.ToList();
Schedule(() =>
{
if (cancellationToken.IsCancellationRequested)
return;
countStatisticsDisplay.Statistics = statistics;
});
}, cancellationToken);
}
private void updateDifficultyStatistics() => Scheduler.AddOnce(() =>
{
if (beatmap.IsDefault)
@@ -321,21 +358,6 @@ namespace osu.Game.Screens.SelectV2
};
});
private void computeStarDifficulty(CancellationToken cancellationToken)
{
difficultyCache.GetDifficultyAsync(beatmap.Value.BeatmapInfo, ruleset.Value, mods.Value, cancellationToken)
.ContinueWith(task =>
{
Schedule(() =>
{
if (cancellationToken.IsCancellationRequested)
return;
starRatingDisplay.Current.Value = task.GetResultSafely() ?? default;
});
}, cancellationToken);
}
protected override void Update()
{
base.Update();