Apply NRT to MemoryCachingComponent classes

This commit is contained in:
Dean Herbert 2023-06-09 18:24:38 +09:00
parent fbbeb3893b
commit 4685ba83e1
4 changed files with 24 additions and 36 deletions

View File

@ -1,13 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Statistics; using osu.Framework.Statistics;
@ -19,8 +17,9 @@ namespace osu.Game.Database
/// Currently not persisted between game sessions. /// Currently not persisted between game sessions.
/// </summary> /// </summary>
public abstract partial class MemoryCachingComponent<TLookup, TValue> : Component public abstract partial class MemoryCachingComponent<TLookup, TValue> : Component
where TLookup : notnull
{ {
private readonly ConcurrentDictionary<TLookup, TValue> cache = new ConcurrentDictionary<TLookup, TValue>(); private readonly ConcurrentDictionary<TLookup, TValue?> cache = new ConcurrentDictionary<TLookup, TValue?>();
private readonly GlobalStatistic<MemoryCachingStatistics> statistics; private readonly GlobalStatistic<MemoryCachingStatistics> statistics;
@ -37,12 +36,12 @@ namespace osu.Game.Database
/// </summary> /// </summary>
/// <param name="lookup">The lookup to retrieve.</param> /// <param name="lookup">The lookup to retrieve.</param>
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param> /// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
protected async Task<TValue> GetAsync([NotNull] TLookup lookup, CancellationToken token = default) protected async Task<TValue?> GetAsync(TLookup lookup, CancellationToken token = default)
{ {
if (CheckExists(lookup, out TValue performance)) if (CheckExists(lookup, out TValue? existing))
{ {
statistics.Value.HitCount++; statistics.Value.HitCount++;
return performance; return existing;
} }
var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false); var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false);
@ -73,7 +72,7 @@ namespace osu.Game.Database
statistics.Value.Usage = cache.Count; statistics.Value.Usage = cache.Count;
} }
protected bool CheckExists([NotNull] TLookup lookup, out TValue value) => protected bool CheckExists(TLookup lookup, [MaybeNullWhen(false)] out TValue value) =>
cache.TryGetValue(lookup, out value); cache.TryGetValue(lookup, out value);
/// <summary> /// <summary>
@ -82,7 +81,7 @@ namespace osu.Game.Database
/// <param name="lookup">The lookup to retrieve.</param> /// <param name="lookup">The lookup to retrieve.</param>
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param> /// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
/// <returns>The computed value.</returns> /// <returns>The computed value.</returns>
protected abstract Task<TValue> ComputeValueAsync(TLookup lookup, CancellationToken token = default); protected abstract Task<TValue?> ComputeValueAsync(TLookup lookup, CancellationToken token = default);
private class MemoryCachingStatistics private class MemoryCachingStatistics
{ {

View File

@ -1,14 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -21,7 +18,7 @@ namespace osu.Game.Database
where TRequest : APIRequest where TRequest : APIRequest
{ {
[Resolved] [Resolved]
private IAPIProvider api { get; set; } private IAPIProvider api { get; set; } = null!;
/// <summary> /// <summary>
/// Creates an <see cref="APIRequest"/> to retrieve the values for a given collection of <typeparamref name="TLookup"/>s. /// Creates an <see cref="APIRequest"/> to retrieve the values for a given collection of <typeparamref name="TLookup"/>s.
@ -32,8 +29,7 @@ namespace osu.Game.Database
/// <summary> /// <summary>
/// Retrieves a list of <typeparamref name="TValue"/>s from a successful <typeparamref name="TRequest"/> created by <see cref="CreateRequest"/>. /// Retrieves a list of <typeparamref name="TValue"/>s from a successful <typeparamref name="TRequest"/> created by <see cref="CreateRequest"/>.
/// </summary> /// </summary>
[CanBeNull] protected abstract IEnumerable<TValue>? RetrieveResults(TRequest request);
protected abstract IEnumerable<TValue> RetrieveResults(TRequest request);
/// <summary> /// <summary>
/// Perform a lookup using the specified <paramref name="id"/>, populating a <typeparamref name="TValue"/>. /// Perform a lookup using the specified <paramref name="id"/>, populating a <typeparamref name="TValue"/>.
@ -41,8 +37,7 @@ namespace osu.Game.Database
/// <param name="id">The ID to lookup.</param> /// <param name="id">The ID to lookup.</param>
/// <param name="token">An optional cancellation token.</param> /// <param name="token">An optional cancellation token.</param>
/// <returns>The populated <typeparamref name="TValue"/>, or null if the value does not exist or the request could not be satisfied.</returns> /// <returns>The populated <typeparamref name="TValue"/>, or null if the value does not exist or the request could not be satisfied.</returns>
[ItemCanBeNull] protected Task<TValue?> LookupAsync(TLookup id, CancellationToken token = default) => GetAsync(id, token);
protected Task<TValue> LookupAsync(TLookup id, CancellationToken token = default) => GetAsync(id, token);
/// <summary> /// <summary>
/// Perform an API lookup on the specified <paramref name="ids"/>, populating a <typeparamref name="TValue"/>. /// Perform an API lookup on the specified <paramref name="ids"/>, populating a <typeparamref name="TValue"/>.
@ -50,9 +45,9 @@ namespace osu.Game.Database
/// <param name="ids">The IDs to lookup.</param> /// <param name="ids">The IDs to lookup.</param>
/// <param name="token">An optional cancellation token.</param> /// <param name="token">An optional cancellation token.</param>
/// <returns>The populated values. May include null results for failed retrievals.</returns> /// <returns>The populated values. May include null results for failed retrievals.</returns>
protected Task<TValue[]> LookupAsync(TLookup[] ids, CancellationToken token = default) protected Task<TValue?[]> LookupAsync(TLookup[] ids, CancellationToken token = default)
{ {
var lookupTasks = new List<Task<TValue>>(); var lookupTasks = new List<Task<TValue?>>();
foreach (var id in ids) foreach (var id in ids)
{ {
@ -69,18 +64,18 @@ namespace osu.Game.Database
} }
// cannot be sealed due to test usages (see TestUserLookupCache). // cannot be sealed due to test usages (see TestUserLookupCache).
protected override async Task<TValue> ComputeValueAsync(TLookup lookup, CancellationToken token = default) protected override async Task<TValue?> ComputeValueAsync(TLookup lookup, CancellationToken token = default)
=> await queryValue(lookup).ConfigureAwait(false); => await queryValue(lookup).ConfigureAwait(false);
private readonly Queue<(TLookup id, TaskCompletionSource<TValue>)> pendingTasks = new Queue<(TLookup, TaskCompletionSource<TValue>)>(); private readonly Queue<(TLookup id, TaskCompletionSource<TValue?>)> pendingTasks = new Queue<(TLookup, TaskCompletionSource<TValue?>)>();
private Task pendingRequestTask; private Task? pendingRequestTask;
private readonly object taskAssignmentLock = new object(); private readonly object taskAssignmentLock = new object();
private Task<TValue> queryValue(TLookup id) private Task<TValue?> queryValue(TLookup id)
{ {
lock (taskAssignmentLock) lock (taskAssignmentLock)
{ {
var tcs = new TaskCompletionSource<TValue>(); var tcs = new TaskCompletionSource<TValue?>();
// Add to the queue. // Add to the queue.
pendingTasks.Enqueue((id, tcs)); pendingTasks.Enqueue((id, tcs));
@ -96,14 +91,14 @@ namespace osu.Game.Database
private async Task performLookup() private async Task performLookup()
{ {
// contains at most 50 unique IDs from tasks, which is used to perform the lookup. // contains at most 50 unique IDs from tasks, which is used to perform the lookup.
var nextTaskBatch = new Dictionary<TLookup, List<TaskCompletionSource<TValue>>>(); var nextTaskBatch = new Dictionary<TLookup, List<TaskCompletionSource<TValue?>>>();
// Grab at most 50 unique IDs from the queue. // Grab at most 50 unique IDs from the queue.
lock (taskAssignmentLock) lock (taskAssignmentLock)
{ {
while (pendingTasks.Count > 0 && nextTaskBatch.Count < 50) while (pendingTasks.Count > 0 && nextTaskBatch.Count < 50)
{ {
(TLookup id, TaskCompletionSource<TValue> task) next = pendingTasks.Dequeue(); (TLookup id, TaskCompletionSource<TValue?> task) next = pendingTasks.Dequeue();
// Perform a secondary check for existence, in case the value was queried in a previous batch. // Perform a secondary check for existence, in case the value was queried in a previous batch.
if (CheckExists(next.id, out var existing)) if (CheckExists(next.id, out var existing))
@ -113,7 +108,7 @@ namespace osu.Game.Database
if (nextTaskBatch.TryGetValue(next.id, out var tasks)) if (nextTaskBatch.TryGetValue(next.id, out var tasks))
tasks.Add(next.task); tasks.Add(next.task);
else else
nextTaskBatch[next.id] = new List<TaskCompletionSource<TValue>> { next.task }; nextTaskBatch[next.id] = new List<TaskCompletionSource<TValue?>> { next.task };
} }
} }
} }

View File

@ -1,13 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
@ -21,8 +18,7 @@ namespace osu.Game.Database
/// <param name="userId">The user to lookup.</param> /// <param name="userId">The user to lookup.</param>
/// <param name="token">An optional cancellation token.</param> /// <param name="token">An optional cancellation token.</param>
/// <returns>The populated user, or null if the user does not exist or the request could not be satisfied.</returns> /// <returns>The populated user, or null if the user does not exist or the request could not be satisfied.</returns>
[ItemCanBeNull] public Task<APIUser?> GetUserAsync(int userId, CancellationToken token = default) => LookupAsync(userId, token);
public Task<APIUser> GetUserAsync(int userId, CancellationToken token = default) => LookupAsync(userId, token);
/// <summary> /// <summary>
/// Perform an API lookup on the specified users, populating a <see cref="APIUser"/> model. /// Perform an API lookup on the specified users, populating a <see cref="APIUser"/> model.
@ -30,10 +26,10 @@ namespace osu.Game.Database
/// <param name="userIds">The users to lookup.</param> /// <param name="userIds">The users to lookup.</param>
/// <param name="token">An optional cancellation token.</param> /// <param name="token">An optional cancellation token.</param>
/// <returns>The populated users. May include null results for failed retrievals.</returns> /// <returns>The populated users. May include null results for failed retrievals.</returns>
public Task<APIUser[]> GetUsersAsync(int[] userIds, CancellationToken token = default) => LookupAsync(userIds, token); public Task<APIUser?[]> GetUsersAsync(int[] userIds, CancellationToken token = default) => LookupAsync(userIds, token);
protected override GetUsersRequest CreateRequest(IEnumerable<int> ids) => new GetUsersRequest(ids.ToArray()); protected override GetUsersRequest CreateRequest(IEnumerable<int> ids) => new GetUsersRequest(ids.ToArray());
protected override IEnumerable<APIUser> RetrieveResults(GetUsersRequest request) => request.Response?.Users; protected override IEnumerable<APIUser>? RetrieveResults(GetUsersRequest request) => request.Response?.Users;
} }
} }

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests