diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index dcfeea5db8..f84dbca0be 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -164,9 +164,9 @@ namespace osu.Game.Tests.Beatmaps { public Func ComputeDifficulty { get; set; } - protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default) + protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default) { - return Task.FromResult(ComputeDifficulty?.Invoke(lookup) ?? new StarDifficulty(BASE_STARS, 0)); + return Task.FromResult(ComputeDifficulty?.Invoke(lookup) ?? new StarDifficulty(BASE_STARS, 0)); } } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 11d589f7bc..e573c96ce9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.SongSelect } } - public override async Task GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo rulesetInfo = null, IEnumerable mods = null, CancellationToken cancellationToken = default) + public override async Task GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo rulesetInfo = null, IEnumerable mods = null, CancellationToken cancellationToken = default) { if (blockCalculation) await calculationBlocker.Task.ConfigureAwait(false); diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 5f20bf49b9..9106408e23 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps /// A component which performs and acts as a central cache for difficulty calculations of beatmap/ruleset/mod combinations. /// Currently not persisted between game sessions. /// - public class BeatmapDifficultyCache : MemoryCachingComponent + public class BeatmapDifficultyCache : MemoryCachingComponent { // Too many simultaneous updates can lead to stutters. One thread seems to work fine for song select display purposes. private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyCache)); @@ -120,9 +120,13 @@ namespace osu.Game.Beatmaps /// The to get the difficulty with. /// The s to get the difficulty with. /// An optional which stops computing the star difficulty. - /// The . - public virtual Task GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null, - [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default) + /// + /// The requested , if non-. + /// A return value indicates that the difficulty process failed or was interrupted early, + /// and as such there is no usable star difficulty value to be returned. + /// + public virtual Task GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null, + [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. rulesetInfo ??= beatmapInfo.Ruleset; @@ -134,13 +138,13 @@ namespace osu.Game.Beatmaps if (localBeatmapInfo == null || localBeatmapInfo.ID == 0 || localRulesetInfo == null) { // If not, fall back to the existing star difficulty (e.g. from an online source). - return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0)); + return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0)); } return GetAsync(new DifficultyCacheLookup(localBeatmapInfo, localRulesetInfo, mods), cancellationToken); } - protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken cancellationToken = default) + protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken cancellationToken = default) { return Task.Factory.StartNew(() => { @@ -151,6 +155,8 @@ namespace osu.Game.Beatmaps }, cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } + protected override bool CacheNullValues => false; + public Task> GetTimedDifficultyAttributesAsync(IWorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken cancellationToken = default) { return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods, cancellationToken), @@ -260,7 +266,7 @@ namespace osu.Game.Beatmaps // We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events. Schedule(() => { - if (!cancellationToken.IsCancellationRequested) + if (!cancellationToken.IsCancellationRequested && t.Result != null) bindable.Value = t.Result; }); }, cancellationToken); @@ -272,7 +278,7 @@ namespace osu.Game.Beatmaps /// The that defines the computation parameters. /// The cancellation token. /// The . - private StarDifficulty computeDifficulty(in DifficultyCacheLookup key, CancellationToken cancellationToken = default) + private StarDifficulty? computeDifficulty(in DifficultyCacheLookup key, CancellationToken cancellationToken = default) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. var beatmapInfo = key.BeatmapInfo; @@ -293,11 +299,11 @@ namespace osu.Game.Beatmaps if (rulesetInfo.Equals(beatmapInfo.Ruleset)) Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); - return new StarDifficulty(); + return null; } catch { - return new StarDifficulty(); + return null; } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9b4216084e..29144e7bdc 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -162,7 +162,12 @@ namespace osu.Game.Scoring // We can compute the max combo locally after the async beatmap difficulty computation. var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); - beatmapMaxCombo = difficulty.MaxCombo; + + // Something failed during difficulty calculation. Fall back to provided score. + if (difficulty == null) + return score.TotalScore; + + beatmapMaxCombo = difficulty.Value.MaxCombo; } } else diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs index 82685e9a04..79fb47f4b0 100644 --- a/osu.Game/Scoring/ScorePerformanceCache.cs +++ b/osu.Game/Scoring/ScorePerformanceCache.cs @@ -37,12 +37,12 @@ namespace osu.Game.Scoring var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, token).ConfigureAwait(false); // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. - if (attributes.Attributes == null) + if (attributes?.Attributes == null) return null; token.ThrowIfCancellationRequested(); - var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Attributes, score); + var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Value.Attributes, score); return calculator?.Calculate(); } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index e31a182a49..3ab2658f97 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -143,14 +143,6 @@ namespace osu.Game.Screens.Ranking.Expanded Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - new StarRatingDisplay(starDifficulty) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft - }, - } }, new FillFlowContainer { @@ -231,6 +223,15 @@ namespace osu.Game.Screens.Ranking.Expanded if (score.Date != default) AddInternal(new PlayedOnText(score.Date)); + if (starDifficulty != null) + { + starAndModDisplay.Add(new StarRatingDisplay(starDifficulty.Value) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }); + } + if (score.Mods.Any()) { starAndModDisplay.Add(new ModDisplay diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 91c8ce441e..edbaba40bc 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -152,7 +152,10 @@ namespace osu.Game.Screens.Select.Details Task.WhenAll(normalStarDifficulty, moddedStarDifficulty).ContinueWith(_ => Schedule(() => { - starDifficulty.Value = ((float)normalStarDifficulty.Result.Stars, (float)moddedStarDifficulty.Result.Stars); + if (normalStarDifficulty.Result == null || moddedStarDifficulty.Result == null) + return; + + starDifficulty.Value = ((float)normalStarDifficulty.Result.Value.Stars, (float)moddedStarDifficulty.Result.Value.Stars); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); }