1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-05 04:52:53 +08:00

Fix async exception by using difficulty calculator directly

This commit is contained in:
Dan Balasescu 2022-09-09 13:57:01 +09:00
parent 3b932b46ca
commit 08d0c08750
4 changed files with 20 additions and 24 deletions

View File

@ -175,7 +175,7 @@ namespace osu.Game
{ {
var score = scoreManager.Query(s => s.ID == id); var score = scoreManager.Query(s => s.ID == id);
await scoreManager.PopulateMaximumStatistics(score); scoreManager.PopulateMaximumStatistics(score);
// Can't use async overload because we're not on the update thread. // Can't use async overload because we're not on the update thread.
// ReSharper disable once MethodHasAsyncOverload // ReSharper disable once MethodHasAsyncOverload

View File

@ -279,7 +279,7 @@ namespace osu.Game
dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig, difficultyCache)); dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig));
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true));

View File

@ -3,11 +3,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Extensions;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -32,17 +31,15 @@ namespace osu.Game.Scoring
private readonly RulesetStore rulesets; private readonly RulesetStore rulesets;
private readonly Func<BeatmapManager> beatmaps; private readonly Func<BeatmapManager> beatmaps;
private readonly BeatmapDifficultyCache? difficultyCache;
private readonly IAPIProvider api; private readonly IAPIProvider api;
public ScoreImporter(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, BeatmapDifficultyCache? difficultyCache = null) public ScoreImporter(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, IAPIProvider api)
: base(storage, realm) : base(storage, realm)
{ {
this.rulesets = rulesets; this.rulesets = rulesets;
this.beatmaps = beatmaps; this.beatmaps = beatmaps;
this.api = api; this.api = api;
this.difficultyCache = difficultyCache;
} }
protected override ScoreInfo? CreateModel(ArchiveReader archive) protected override ScoreInfo? CreateModel(ArchiveReader archive)
@ -77,7 +74,7 @@ namespace osu.Game.Scoring
if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo)); if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo));
if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset)); if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset));
PopulateMaximumStatistics(model).WaitSafely(); PopulateMaximumStatistics(model);
if (string.IsNullOrEmpty(model.StatisticsJson)) if (string.IsNullOrEmpty(model.StatisticsJson))
model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics);
@ -90,16 +87,19 @@ namespace osu.Game.Scoring
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>. /// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
/// </summary> /// </summary>
/// <param name="score">The score to populate the statistics of.</param> /// <param name="score">The score to populate the statistics of.</param>
public async Task PopulateMaximumStatistics(ScoreInfo score) public void PopulateMaximumStatistics(ScoreInfo score)
{ {
if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0) if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0)
return; return;
var beatmap = score.BeatmapInfo.Detach(); var beatmap = score.BeatmapInfo.Detach();
var ruleset = score.Ruleset.Detach(); var ruleset = score.Ruleset.Detach();
var rulesetInstance = ruleset.CreateInstance();
Debug.Assert(rulesetInstance != null);
// Populate the maximum statistics. // Populate the maximum statistics.
HitResult maxBasicResult = ruleset.CreateInstance().GetHitResults() HitResult maxBasicResult = rulesetInstance.GetHitResults()
.Select(h => h.result) .Select(h => h.result)
.Where(h => h.IsBasic()) .Where(h => h.IsBasic())
.OrderByDescending(Judgement.ToNumericResult).First(); .OrderByDescending(Judgement.ToNumericResult).First();
@ -134,18 +134,14 @@ namespace osu.Game.Scoring
return; return;
#pragma warning disable CS0618 #pragma warning disable CS0618
if (difficultyCache == null)
throw new InvalidOperationException($"Cannot populate legacy score statistics without a {nameof(BeatmapDifficultyCache)}.");
// In osu! and osu!mania, some judgements affect combo but aren't stored to scores. // In osu! and osu!mania, some judgements affect combo but aren't stored to scores.
// A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes. // A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes.
StarDifficulty? difficulty = await difficultyCache.GetDifficultyAsync(beatmap, ruleset, score.Mods); var calculator = rulesetInstance.CreateDifficultyCalculator(beatmaps().GetWorkingBeatmap(beatmap));
if (difficulty == null) var attributes = calculator.Calculate(score.Mods);
throw new InvalidOperationException("Failed to populate maximum statistics due to missing difficulty attributes.");
int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum(); int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum();
if (difficulty.Value.MaxCombo > maxComboFromStatistics) if (attributes.MaxCombo > maxComboFromStatistics)
score.MaximumStatistics[HitResult.LegacyComboIncrease] = difficulty.Value.MaxCombo - maxComboFromStatistics; score.MaximumStatistics[HitResult.LegacyComboIncrease] = attributes.MaxCombo - maxComboFromStatistics;
#pragma warning restore CS0618 #pragma warning restore CS0618
} }

View File

@ -29,12 +29,12 @@ namespace osu.Game.Scoring
private readonly ScoreImporter scoreImporter; private readonly ScoreImporter scoreImporter;
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, IAPIProvider api,
OsuConfigManager configManager = null, BeatmapDifficultyCache difficultyCache = null) OsuConfigManager configManager = null)
: base(storage, realm) : base(storage, realm)
{ {
this.configManager = configManager; this.configManager = configManager;
scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api, difficultyCache) scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api)
{ {
PostNotification = obj => PostNotification?.Invoke(obj) PostNotification = obj => PostNotification?.Invoke(obj)
}; };
@ -183,7 +183,7 @@ namespace osu.Game.Scoring
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>. /// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
/// </summary> /// </summary>
/// <param name="score">The score to populate the statistics of.</param> /// <param name="score">The score to populate the statistics of.</param>
public Task PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score); public void PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score);
#region Implementation of IPresentImports<ScoreInfo> #region Implementation of IPresentImports<ScoreInfo>