Don't store BeatmapInfo/RulesetInfo references, remove TimedExpiryCache

This commit is contained in:
smoogipoo
2020-07-21 23:50:54 +09:00
parent 107b5ca4f2
commit 00e6217f60

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -24,7 +25,7 @@ namespace osu.Game.Beatmaps
private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyManager)); private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyManager));
// A cache that keeps references to BeatmapInfos for 60sec. // A cache that keeps references to BeatmapInfos for 60sec.
private readonly TimedExpiryCache<DifficultyCacheLookup, StarDifficulty> difficultyCache = new TimedExpiryCache<DifficultyCacheLookup, StarDifficulty> { ExpiryTime = 60000 }; private readonly ConcurrentDictionary<DifficultyCacheLookup, StarDifficulty> difficultyCache = new ConcurrentDictionary<DifficultyCacheLookup, StarDifficulty>();
// All bindables that should be updated along with the current ruleset + mods. // All bindables that should be updated along with the current ruleset + mods.
private readonly LockedWeakList<BindableStarDifficulty> trackedBindables = new LockedWeakList<BindableStarDifficulty>(); private readonly LockedWeakList<BindableStarDifficulty> trackedBindables = new LockedWeakList<BindableStarDifficulty>();
@ -91,7 +92,8 @@ namespace osu.Game.Beatmaps
if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key))
return existing; return existing;
return await Task.Factory.StartNew(() => computeDifficulty(key), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); return await Task.Factory.StartNew(() => computeDifficulty(key, beatmapInfo, rulesetInfo), cancellationToken,
TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
} }
/// <summary> /// <summary>
@ -106,7 +108,7 @@ namespace osu.Game.Beatmaps
if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key)) if (tryGetGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key))
return existing; return existing;
return computeDifficulty(key); return computeDifficulty(key, beatmapInfo, rulesetInfo);
} }
private CancellationTokenSource trackedUpdateCancellationSource; private CancellationTokenSource trackedUpdateCancellationSource;
@ -169,28 +171,24 @@ namespace osu.Game.Beatmaps
/// Computes the difficulty defined by a <see cref="DifficultyCacheLookup"/> key, and stores it to the timed cache. /// Computes the difficulty defined by a <see cref="DifficultyCacheLookup"/> key, and stores it to the timed cache.
/// </summary> /// </summary>
/// <param name="key">The <see cref="DifficultyCacheLookup"/> that defines the computation parameters.</param> /// <param name="key">The <see cref="DifficultyCacheLookup"/> that defines the computation parameters.</param>
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to compute the difficulty of.</param>
/// <param name="rulesetInfo">The <see cref="RulesetInfo"/> to compute the difficulty with.</param>
/// <returns>The <see cref="StarDifficulty"/>.</returns> /// <returns>The <see cref="StarDifficulty"/>.</returns>
private StarDifficulty computeDifficulty(in DifficultyCacheLookup key) private StarDifficulty computeDifficulty(in DifficultyCacheLookup key, BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo)
{ {
try try
{ {
var ruleset = key.RulesetInfo.CreateInstance(); var ruleset = rulesetInfo.CreateInstance();
Debug.Assert(ruleset != null); Debug.Assert(ruleset != null);
var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo)); var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(beatmapInfo));
var attributes = calculator.Calculate(key.Mods); var attributes = calculator.Calculate(key.Mods);
var difficulty = new StarDifficulty(attributes.StarRating); return difficultyCache[key] = new StarDifficulty(attributes.StarRating);
difficultyCache.Add(key, difficulty);
return difficulty;
} }
catch catch
{ {
var difficulty = new StarDifficulty(0); return difficultyCache[key] = new StarDifficulty(0);
difficultyCache.Add(key, difficulty);
return difficulty;
} }
} }
@ -208,8 +206,8 @@ namespace osu.Game.Beatmaps
// In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
rulesetInfo ??= beatmapInfo.Ruleset; rulesetInfo ??= beatmapInfo.Ruleset;
// Difficulty can only be computed if the beatmap is locally available. // Difficulty can only be computed if the beatmap and ruleset are locally available.
if (beatmapInfo.ID == 0) if (beatmapInfo.ID == 0 || rulesetInfo.ID == null)
{ {
existingDifficulty = new StarDifficulty(0); existingDifficulty = new StarDifficulty(0);
key = default; key = default;
@ -217,33 +215,34 @@ namespace osu.Game.Beatmaps
return true; return true;
} }
key = new DifficultyCacheLookup(beatmapInfo, rulesetInfo, mods); key = new DifficultyCacheLookup(beatmapInfo.ID, rulesetInfo.ID.Value, mods);
return difficultyCache.TryGetValue(key, out existingDifficulty); return difficultyCache.TryGetValue(key, out existingDifficulty);
} }
private readonly struct DifficultyCacheLookup : IEquatable<DifficultyCacheLookup> private readonly struct DifficultyCacheLookup : IEquatable<DifficultyCacheLookup>
{ {
public readonly BeatmapInfo BeatmapInfo; public readonly int BeatmapId;
public readonly RulesetInfo RulesetInfo; public readonly int RulesetId;
public readonly Mod[] Mods; public readonly Mod[] Mods;
public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo, IEnumerable<Mod> mods) public DifficultyCacheLookup(int beatmapId, int rulesetId, IEnumerable<Mod> mods)
{ {
BeatmapInfo = beatmapInfo; BeatmapId = beatmapId;
RulesetInfo = rulesetInfo; RulesetId = rulesetId;
Mods = mods?.OrderBy(m => m.Acronym).ToArray() ?? Array.Empty<Mod>(); Mods = mods?.OrderBy(m => m.Acronym).ToArray() ?? Array.Empty<Mod>();
} }
public bool Equals(DifficultyCacheLookup other) public bool Equals(DifficultyCacheLookup other)
=> BeatmapInfo.Equals(other.BeatmapInfo) => BeatmapId == other.BeatmapId
&& RulesetId == other.RulesetId
&& Mods.SequenceEqual(other.Mods); && Mods.SequenceEqual(other.Mods);
public override int GetHashCode() public override int GetHashCode()
{ {
var hashCode = new HashCode(); var hashCode = new HashCode();
hashCode.Add(BeatmapInfo.Hash); hashCode.Add(BeatmapId);
hashCode.Add(RulesetInfo.GetHashCode()); hashCode.Add(RulesetId);
foreach (var mod in Mods) foreach (var mod in Mods)
hashCode.Add(mod.Acronym); hashCode.Add(mod.Acronym);