// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Database; namespace osu.Game.Scoring { public partial class ScoreManager { private class UserIdLookupCache : MemoryCachingComponent { private readonly IAPIProvider api; public UserIdLookupCache(IAPIProvider api) { this.api = api; } /// /// Perform an API lookup on the specified username, returning the associated ID. /// /// The username to lookup. /// An optional cancellation token. /// The user ID, or 1 if the user does not exist or the request could not be satisfied. public Task GetUserIdAsync(string username, CancellationToken token = default) => GetAsync(username, token); protected override async Task ComputeValueAsync(string lookup, CancellationToken token = default) => await queryUserId(lookup).ConfigureAwait(false); private readonly Queue<(string username, TaskCompletionSource)> pendingUserTasks = new Queue<(string, TaskCompletionSource)>(); private Task pendingRequestTask; private readonly object taskAssignmentLock = new object(); private Task queryUserId(string username) { lock (taskAssignmentLock) { var tcs = new TaskCompletionSource(); // Add to the queue. pendingUserTasks.Enqueue((username, tcs)); // Create a request task if there's not already one. if (pendingRequestTask == null) createNewTask(); return tcs.Task; } } private void performLookup() { (string username, TaskCompletionSource task) next; lock (taskAssignmentLock) { next = pendingUserTasks.Dequeue(); } var request = new GetUserRequest(next.username); // rather than queueing, we maintain our own single-threaded request stream. // todo: we probably want retry logic here. api.Perform(request); // Create a new request task if there's still more users to query. lock (taskAssignmentLock) { pendingRequestTask = null; if (pendingUserTasks.Count > 0) createNewTask(); } next.task.SetResult(request.Result?.Id ?? 1); } private void createNewTask() => pendingRequestTask = Task.Run(performLookup); } } }