diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index c93640e7b5..2bb3129f68 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -11,6 +11,7 @@ using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
@@ -39,10 +40,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
private TestMultiplayer multiplayerScreen;
private TestMultiplayerClient client;
- public TestSceneMultiplayer()
- {
- loadMultiplayer();
- }
+ [Cached(typeof(UserLookupCache))]
+ private UserLookupCache lookupCache = new TestUserLookupCache();
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@@ -51,18 +50,43 @@ namespace osu.Game.Tests.Visual.Multiplayer
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
}
- [SetUp]
- public void Setup() => Schedule(() =>
+ public override void SetUpSteps()
{
- beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
- importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
- });
+ base.SetUpSteps();
+
+ AddStep("import beatmap", () =>
+ {
+ beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
+ importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
+ });
+
+ AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
+
+ AddStep("load dependencies", () =>
+ {
+ client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
+
+ // The screen gets suspended so it stops receiving updates.
+ Child = client;
+
+ LoadScreen(dependenciesScreen = new DependenciesScreen(client));
+ });
+
+ AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
+
+ AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
+ AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
+ }
+
+ [Test]
+ public void TestEmpty()
+ {
+ // used to test the flow of multiplayer from visual tests.
+ }
[Test]
public void TestUserSetToIdleWhenBeatmapDeleted()
{
- loadMultiplayer();
-
createRoom(() => new Room
{
Name = { Value = "Test Room" },
@@ -85,8 +109,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap()
{
- loadMultiplayer();
-
createRoom(() => new Room
{
Name = { Value = "Test Room" },
@@ -123,8 +145,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestLocalPlayStartsWhileSpectatingWhenBeatmapBecomesAvailable()
{
- loadMultiplayer();
-
createRoom(() => new Room
{
Name = { Value = "Test Room" },
@@ -167,8 +187,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestLeaveNavigation()
{
- loadMultiplayer();
-
createRoom(() => new Room
{
Name = { Value = "Test Room" },
@@ -227,26 +245,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for join", () => client.Room != null);
}
- private void loadMultiplayer()
- {
- AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
-
- AddStep("load dependencies", () =>
- {
- client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
-
- // The screen gets suspended so it stops receiving updates.
- Child = client;
-
- LoadScreen(dependenciesScreen = new DependenciesScreen(client));
- });
-
- AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
-
- AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
- AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
- }
-
///
/// Used for the sole purpose of adding as a resolvable dependency.
///
diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs
index 19cc211709..13c37ddfe9 100644
--- a/osu.Game/Database/UserLookupCache.cs
+++ b/osu.Game/Database/UserLookupCache.cs
@@ -27,6 +27,30 @@ namespace osu.Game.Database
[ItemCanBeNull]
public Task GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token);
+ ///
+ /// Perform an API lookup on the specified users, populating a model.
+ ///
+ /// The users to lookup.
+ /// An optional cancellation token.
+ /// The populated users. May include null results for failed retrievals.
+ public Task GetUsersAsync(int[] userIds, CancellationToken token = default)
+ {
+ var userLookupTasks = new List>();
+
+ foreach (var u in userIds)
+ {
+ userLookupTasks.Add(GetUserAsync(u, token).ContinueWith(task =>
+ {
+ if (!task.IsCompletedSuccessfully)
+ return null;
+
+ return task.Result;
+ }, token));
+ }
+
+ return Task.WhenAll(userLookupTasks);
+ }
+
protected override async Task ComputeValueAsync(int lookup, CancellationToken token = default)
=> await queryUser(lookup).ConfigureAwait(false);
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
index 1bbe49a705..043cce4630 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
@@ -125,9 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
const float padding = 44; // enough margin to avoid the hit error display.
- leaderboard.Position = new Vector2(
- padding,
- padding + HUDOverlay.TopScoringElementsHeight);
+ leaderboard.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
}
private void onMatchStarted() => Scheduler.Add(() =>
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs
index ab3ead68b5..55c4270c70 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public void AddClock(int userId, IClock clock)
{
if (!UserScores.TryGetValue(userId, out var data))
- return;
+ throw new ArgumentException(@"Provided user is not tracked by this leaderboard", nameof(userId));
((SpectatingTrackedUserData)data).Clock = clock;
}
@@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public void RemoveClock(int userId)
{
if (!UserScores.TryGetValue(userId, out var data))
- return;
+ throw new ArgumentException(@"Provided user is not tracked by this leaderboard", nameof(userId));
((SpectatingTrackedUserData)data).Clock = null;
}
diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
index c3bfe19b29..a10c16fcd5 100644
--- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
+++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
@@ -55,20 +55,27 @@ namespace osu.Game.Screens.Play.HUD
foreach (var userId in playingUsers)
{
- // probably won't be required in the final implementation.
- var resolvedUser = userLookupCache.GetUserAsync(userId).Result;
-
var trackedUser = CreateUserData(userId, scoreProcessor);
trackedUser.ScoringMode.BindTo(scoringMode);
-
- var leaderboardScore = AddPlayer(resolvedUser, resolvedUser?.Id == api.LocalUser.Value.Id);
- leaderboardScore.Accuracy.BindTo(trackedUser.Accuracy);
- leaderboardScore.TotalScore.BindTo(trackedUser.Score);
- leaderboardScore.Combo.BindTo(trackedUser.CurrentCombo);
- leaderboardScore.HasQuit.BindTo(trackedUser.UserQuit);
-
UserScores[userId] = trackedUser;
}
+
+ userLookupCache.GetUsersAsync(playingUsers.ToArray()).ContinueWith(users => Schedule(() =>
+ {
+ foreach (var user in users.Result)
+ {
+ if (user == null)
+ continue;
+
+ var trackedUser = UserScores[user.Id];
+
+ var leaderboardScore = AddPlayer(user, user.Id == api.LocalUser.Value.Id);
+ leaderboardScore.Accuracy.BindTo(trackedUser.Accuracy);
+ leaderboardScore.TotalScore.BindTo(trackedUser.Score);
+ leaderboardScore.Combo.BindTo(trackedUser.CurrentCombo);
+ leaderboardScore.HasQuit.BindTo(trackedUser.UserQuit);
+ }
+ }));
}
protected override void LoadComplete()
@@ -84,6 +91,8 @@ namespace osu.Game.Screens.Play.HUD
usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId }));
}
+ // bind here is to support players leaving the match.
+ // new players are not supported.
playingUsers.BindTo(multiplayerClient.CurrentMatchPlayingUserIds);
playingUsers.BindCollectionChanged(usersChanged);
diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs
index 8fc9222f59..b6eafe496f 100644
--- a/osu.Game/Screens/Spectate/SpectatorScreen.cs
+++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -61,10 +60,15 @@ namespace osu.Game.Screens.Spectate
{
base.LoadComplete();
- getAllUsers().ContinueWith(users => Schedule(() =>
+ userLookupCache.GetUsersAsync(userIds.ToArray()).ContinueWith(users => Schedule(() =>
{
foreach (var u in users.Result)
+ {
+ if (u == null)
+ continue;
+
userMap[u.Id] = u;
+ }
playingUserStates.BindTo(spectatorClient.PlayingUserStates);
playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true);
@@ -77,24 +81,6 @@ namespace osu.Game.Screens.Spectate
}));
}
- private Task getAllUsers()
- {
- var userLookupTasks = new List>();
-
- foreach (var u in userIds)
- {
- userLookupTasks.Add(userLookupCache.GetUserAsync(u).ContinueWith(task =>
- {
- if (!task.IsCompletedSuccessfully)
- return null;
-
- return task.Result;
- }));
- }
-
- return Task.WhenAll(userLookupTasks);
- }
-
private void beatmapUpdated(ValueChangedEvent> e)
{
if (!e.NewValue.TryGetTarget(out var beatmapSet))