diff --git a/osu.Game/Database/MemoryCachingComponent.cs b/osu.Game/Database/MemoryCachingComponent.cs index 85cf3b8af1..afdf37fa27 100644 --- a/osu.Game/Database/MemoryCachingComponent.cs +++ b/osu.Game/Database/MemoryCachingComponent.cs @@ -2,6 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Graphics; namespace osu.Game.Database @@ -12,6 +15,34 @@ namespace osu.Game.Database /// public abstract class MemoryCachingComponent : Component { - protected readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); + private readonly ConcurrentDictionary cache = new ConcurrentDictionary(); + + protected virtual bool CacheNullValues => true; + + /// + /// Retrieve the cached value for the given . + /// + /// The lookup to retrieve. + /// An optional to cancel the operation. + protected async Task GetAsync([NotNull] TLookup lookup, CancellationToken token = default) + { + if (cache.TryGetValue(lookup, out TValue performance)) + return performance; + + var computed = await ComputeValueAsync(lookup, token); + + if (computed != null || CacheNullValues) + cache[lookup] = computed; + + return computed; + } + + /// + /// Called on cache miss to compute the value for the specified lookup. + /// + /// The lookup to retrieve. + /// An optional to cancel the operation. + /// The computed value. + protected abstract Task ComputeValueAsync(TLookup lookup, CancellationToken token = default); } } diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs index 435b93d7af..5f66c13d2f 100644 --- a/osu.Game/Scoring/ScorePerformanceCache.cs +++ b/osu.Game/Scoring/ScorePerformanceCache.cs @@ -15,28 +15,25 @@ namespace osu.Game.Scoring /// A component which performs and acts as a central cache for performance calculations of locally databased scores. /// Currently not persisted between game sessions. /// - public class ScorePerformanceCache : MemoryCachingComponent + public class ScorePerformanceCache : MemoryCachingComponent { [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } + protected override bool CacheNullValues => false; + /// /// Calculates performance for the given . /// /// The score to do the calculation on. /// An optional to cancel the operation. - public Task CalculatePerformanceAsync([NotNull] ScoreInfo score, CancellationToken token = default) + public Task CalculatePerformanceAsync([NotNull] ScoreInfo score, CancellationToken token = default) => + GetAsync(new PerformanceCacheLookup(score), token); + + protected override async Task ComputeValueAsync(PerformanceCacheLookup lookup, CancellationToken token = default) { - var lookupKey = new PerformanceCacheLookup(score); + var score = lookup.ScoreInfo; - if (Cache.TryGetValue(lookupKey, out double performance)) - return Task.FromResult((double?)performance); - - return computePerformanceAsync(score, lookupKey, token); - } - - private async Task computePerformanceAsync(ScoreInfo score, PerformanceCacheLookup lookupKey, CancellationToken token = default) - { var attributes = await difficultyCache.GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, token); // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. @@ -46,31 +43,25 @@ namespace osu.Game.Scoring token.ThrowIfCancellationRequested(); var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Attributes, score); - var total = calculator?.Calculate(); - if (total.HasValue) - Cache[lookupKey] = total.Value; - - return total; + return calculator?.Calculate(); } public readonly struct PerformanceCacheLookup { - public readonly string ScoreHash; - public readonly int LocalScoreID; + public readonly ScoreInfo ScoreInfo; public PerformanceCacheLookup(ScoreInfo info) { - ScoreHash = info.Hash; - LocalScoreID = info.ID; + ScoreInfo = info; } public override int GetHashCode() { var hash = new HashCode(); - hash.Add(ScoreHash); - hash.Add(LocalScoreID); + hash.Add(ScoreInfo.Hash); + hash.Add(ScoreInfo.ID); return hash.ToHashCode(); }