diff --git a/osu.Game/Scoring/ScorePerformanceManager.cs b/osu.Game/Scoring/ScorePerformanceManager.cs index c8fec3b40c..0a57ccbd1f 100644 --- a/osu.Game/Scoring/ScorePerformanceManager.cs +++ b/osu.Game/Scoring/ScorePerformanceManager.cs @@ -1,17 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; namespace osu.Game.Scoring { public class ScorePerformanceManager : Component { + private readonly ConcurrentDictionary performanceCache = new ConcurrentDictionary(); + [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -22,18 +27,73 @@ namespace osu.Game.Scoring /// An optional to cancel the operation. public async Task CalculatePerformanceAsync([NotNull] ScoreInfo score, CancellationToken token = default) { + if (score.PP.HasValue) + return score.PP.Value; + + if (tryGetExisting(score, out var perf, out var lookupKey)) + return perf; + return await Task.Factory.StartNew(() => { - if (token.IsCancellationRequested) - return default; - - var beatmap = beatmapManager.GetWorkingBeatmap(score.Beatmap); - - var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(beatmap, score); - var total = calculator.Calculate(); - - return total; + return computePerformance(score, lookupKey, token); }, token); } + + private bool tryGetExisting(ScoreInfo score, out double performance, out PerformanceCacheLookup lookupKey) + { + lookupKey = new PerformanceCacheLookup(score); + + return performanceCache.TryGetValue(lookupKey, out performance); + } + + private double computePerformance(ScoreInfo score, PerformanceCacheLookup lookupKey, CancellationToken token = default) + { + var beatmap = beatmapManager.GetWorkingBeatmap(score.Beatmap); + + if (token.IsCancellationRequested) + return default; + + var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(beatmap, score); + var total = calculator.Calculate(); + + performanceCache[lookupKey] = total; + + return total; + } + + public readonly struct PerformanceCacheLookup + { + public readonly double Accuracy; + public readonly int BeatmapId; + public readonly long TotalScore; + public readonly int Combo; + public readonly Mod[] Mods; + public readonly int RulesetId; + + public PerformanceCacheLookup(ScoreInfo info) + { + Accuracy = info.Accuracy; + BeatmapId = info.Beatmap.ID; + TotalScore = info.TotalScore; + Combo = info.Combo; + Mods = info.Mods; + RulesetId = info.Ruleset.ID.Value; + } + + public override int GetHashCode() + { + var hash = new HashCode(); + + hash.Add(Accuracy); + hash.Add(BeatmapId); + hash.Add(TotalScore); + hash.Add(Combo); + hash.Add(RulesetId); + foreach (var mod in Mods) + hash.Add(mod); + + return hash.ToHashCode(); + } + } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index e014258fd4..6c2ad5844b 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -25,15 +25,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics [BackgroundDependencyLoader] private void load(ScorePerformanceManager performanceManager) { - if (score.PP.HasValue) - { - performance.Value = (int)score.PP.Value; - } - else - { - performanceManager.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => performance.Value = (int)t.Result), cancellationTokenSource.Token); - } + performanceManager.CalculatePerformanceAsync(score, cancellationTokenSource.Token) + .ContinueWith(t => Schedule(() => performance.Value = (int)t.Result), cancellationTokenSource.Token); } public override void Appear()