// 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.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Users; namespace osu.Game.Database { public class UserLookupCache : MemoryCachingComponent { [Resolved] private IAPIProvider api { get; set; } public Task GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token); protected override async Task ComputeValueAsync(int lookup, CancellationToken token = default) => await queryUser(lookup); private readonly Queue<(int id, TaskCompletionSource)> pendingUserTasks = new Queue<(int, TaskCompletionSource)>(); private Task pendingRequestTask; private readonly object taskAssignmentLock = new object(); private Task queryUser(int userId) { lock (taskAssignmentLock) { var tcs = new TaskCompletionSource(); // Add to the queue. pendingUserTasks.Enqueue((userId, tcs)); // Create a request task if there's not already one. if (pendingRequestTask == null) createNewTask(); return tcs.Task; } } private void performLookup() { var userTasks = new List<(int id, TaskCompletionSource task)>(); // Grab at most 50 users from the queue. lock (taskAssignmentLock) { while (pendingUserTasks.Count > 0 && userTasks.Count < 50) { (int id, TaskCompletionSource task) next = pendingUserTasks.Dequeue(); // Perform a secondary check for existence, in case the user was queried in a previous batch. if (CheckExists(next.id, out var existing)) next.task.SetResult(existing); else userTasks.Add(next); } } // Query the users. var request = new GetUsersRequest(userTasks.Select(t => t.id).ToArray()); // rather than queueing, we maintain our own single-threaded request stream. 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(); } // Notify of completion. foreach (var (id, task) in userTasks) task.SetResult(request.Result?.Users?.FirstOrDefault(u => u.Id == id)); } private void createNewTask() => pendingRequestTask = Task.Run(performLookup); } }