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