Merge pull request #22032 from frenzibyte/fix-potential-difficulty-failure-alt

Fix advanced statistics display potentially performing invalid difficulty calculation
This commit is contained in:
Dean Herbert 2023-01-23 15:39:47 +09:00 committed by GitHub
commit e3932c077b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 27 additions and 9 deletions

View File

@ -160,9 +160,12 @@ namespace osu.Game
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } // cached via load() method protected Bindable<WorkingBeatmap> Beatmap { get; private set; } // cached via load() method
/// <summary>
/// The current ruleset selection for the local user.
/// </summary>
[Cached] [Cached]
[Cached(typeof(IBindable<RulesetInfo>))] [Cached(typeof(IBindable<RulesetInfo>))]
protected readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>(); protected internal readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
/// <summary> /// <summary>
/// The current mod selection for the local user. /// The current mod selection for the local user.

View File

@ -30,14 +30,16 @@ namespace osu.Game.Screens.Select.Details
{ {
public partial class AdvancedStats : Container public partial class AdvancedStats : Container
{ {
[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; }
[Resolved] [Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; } private IBindable<IReadOnlyList<Mod>> mods { get; set; }
[Resolved] [Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } private OsuGameBase game { get; set; }
[Resolved] private IBindable<RulesetInfo> gameRuleset;
private BeatmapDifficultyCache difficultyCache { get; set; }
protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate;
private readonly StatisticRow starDifficulty; private readonly StatisticRow starDifficulty;
@ -84,7 +86,13 @@ namespace osu.Game.Screens.Select.Details
{ {
base.LoadComplete(); base.LoadComplete();
ruleset.BindValueChanged(_ => updateStatistics()); // the cached ruleset bindable might be a decoupled bindable provided by SongSelect,
// which we can't rely on in combination with the game-wide selected mods list,
// since mods could be updated to the new ruleset instances while the decoupled bindable is held behind,
// therefore resulting in performing difficulty calculation with invalid states.
gameRuleset = game.Ruleset.GetBoundCopy();
gameRuleset.BindValueChanged(_ => updateStatistics());
mods.BindValueChanged(modsChanged, true); mods.BindValueChanged(modsChanged, true);
} }
@ -142,7 +150,14 @@ namespace osu.Game.Screens.Select.Details
private CancellationTokenSource starDifficultyCancellationSource; private CancellationTokenSource starDifficultyCancellationSource;
private void updateStarDifficulty() /// <summary>
/// Updates the displayed star difficulty statistics with the values provided by the currently-selected beatmap, ruleset, and selected mods.
/// </summary>
/// <remarks>
/// This is scheduled to avoid scenarios wherein a ruleset changes first before selected mods do,
/// potentially resulting in failure during difficulty calculation due to incomplete bindable state updates.
/// </remarks>
private void updateStarDifficulty() => Scheduler.AddOnce(() =>
{ {
starDifficultyCancellationSource?.Cancel(); starDifficultyCancellationSource?.Cancel();
@ -151,8 +166,8 @@ namespace osu.Game.Screens.Select.Details
starDifficultyCancellationSource = new CancellationTokenSource(); starDifficultyCancellationSource = new CancellationTokenSource();
var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, null, starDifficultyCancellationSource.Token); var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, gameRuleset.Value, null, starDifficultyCancellationSource.Token);
var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, gameRuleset.Value, mods.Value, starDifficultyCancellationSource.Token);
Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() => Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() =>
{ {
@ -164,7 +179,7 @@ namespace osu.Game.Screens.Select.Details
starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars); starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars);
}), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);
} });
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {