Merge pull request #13786 from peppy/multiplayer-test-fixes

Fix multiplayer test failures due to leaderboard load process
This commit is contained in:
Dan Balasescu 2021-07-06 16:33:41 +09:00 committed by GitHub
commit 32e6c9c5d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 72 deletions

View File

@ -11,6 +11,7 @@ using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
@ -39,10 +40,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
private TestMultiplayer multiplayerScreen; private TestMultiplayer multiplayerScreen;
private TestMultiplayerClient client; private TestMultiplayerClient client;
public TestSceneMultiplayer() [Cached(typeof(UserLookupCache))]
{ private UserLookupCache lookupCache = new TestUserLookupCache();
loadMultiplayer();
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio) 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)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
} }
[SetUp] public override void SetUpSteps()
public void Setup() => Schedule(() =>
{ {
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); base.SetUpSteps();
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
}); 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] [Test]
public void TestUserSetToIdleWhenBeatmapDeleted() public void TestUserSetToIdleWhenBeatmapDeleted()
{ {
loadMultiplayer();
createRoom(() => new Room createRoom(() => new Room
{ {
Name = { Value = "Test Room" }, Name = { Value = "Test Room" },
@ -85,8 +109,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test] [Test]
public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap() public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap()
{ {
loadMultiplayer();
createRoom(() => new Room createRoom(() => new Room
{ {
Name = { Value = "Test Room" }, Name = { Value = "Test Room" },
@ -123,8 +145,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test] [Test]
public void TestLocalPlayStartsWhileSpectatingWhenBeatmapBecomesAvailable() public void TestLocalPlayStartsWhileSpectatingWhenBeatmapBecomesAvailable()
{ {
loadMultiplayer();
createRoom(() => new Room createRoom(() => new Room
{ {
Name = { Value = "Test Room" }, Name = { Value = "Test Room" },
@ -167,8 +187,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test] [Test]
public void TestLeaveNavigation() public void TestLeaveNavigation()
{ {
loadMultiplayer();
createRoom(() => new Room createRoom(() => new Room
{ {
Name = { Value = "Test Room" }, Name = { Value = "Test Room" },
@ -227,26 +245,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for join", () => client.Room != null); 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);
}
/// <summary> /// <summary>
/// Used for the sole purpose of adding <see cref="TestMultiplayerClient"/> as a resolvable dependency. /// Used for the sole purpose of adding <see cref="TestMultiplayerClient"/> as a resolvable dependency.
/// </summary> /// </summary>

View File

@ -27,6 +27,30 @@ namespace osu.Game.Database
[ItemCanBeNull] [ItemCanBeNull]
public Task<User> GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token); public Task<User> GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token);
/// <summary>
/// Perform an API lookup on the specified users, populating a <see cref="User"/> model.
/// </summary>
/// <param name="userIds">The users to lookup.</param>
/// <param name="token">An optional cancellation token.</param>
/// <returns>The populated users. May include null results for failed retrievals.</returns>
public Task<User[]> GetUsersAsync(int[] userIds, CancellationToken token = default)
{
var userLookupTasks = new List<Task<User>>();
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<User> ComputeValueAsync(int lookup, CancellationToken token = default) protected override async Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
=> await queryUser(lookup).ConfigureAwait(false); => await queryUser(lookup).ConfigureAwait(false);

View File

@ -125,9 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{ {
const float padding = 44; // enough margin to avoid the hit error display. const float padding = 44; // enough margin to avoid the hit error display.
leaderboard.Position = new Vector2( leaderboard.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
padding,
padding + HUDOverlay.TopScoringElementsHeight);
} }
private void onMatchStarted() => Scheduler.Add(() => private void onMatchStarted() => Scheduler.Add(() =>

View File

@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public void AddClock(int userId, IClock clock) public void AddClock(int userId, IClock clock)
{ {
if (!UserScores.TryGetValue(userId, out var data)) 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; ((SpectatingTrackedUserData)data).Clock = clock;
} }
@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public void RemoveClock(int userId) public void RemoveClock(int userId)
{ {
if (!UserScores.TryGetValue(userId, out var data)) 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; ((SpectatingTrackedUserData)data).Clock = null;
} }

View File

@ -55,20 +55,27 @@ namespace osu.Game.Screens.Play.HUD
foreach (var userId in playingUsers) 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); var trackedUser = CreateUserData(userId, scoreProcessor);
trackedUser.ScoringMode.BindTo(scoringMode); 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; 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() protected override void LoadComplete()
@ -84,6 +91,8 @@ namespace osu.Game.Screens.Play.HUD
usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId })); 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.BindTo(multiplayerClient.CurrentMatchPlayingUserIds);
playingUsers.BindCollectionChanged(usersChanged); playingUsers.BindCollectionChanged(usersChanged);

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -61,10 +60,15 @@ namespace osu.Game.Screens.Spectate
{ {
base.LoadComplete(); base.LoadComplete();
getAllUsers().ContinueWith(users => Schedule(() => userLookupCache.GetUsersAsync(userIds.ToArray()).ContinueWith(users => Schedule(() =>
{ {
foreach (var u in users.Result) foreach (var u in users.Result)
{
if (u == null)
continue;
userMap[u.Id] = u; userMap[u.Id] = u;
}
playingUserStates.BindTo(spectatorClient.PlayingUserStates); playingUserStates.BindTo(spectatorClient.PlayingUserStates);
playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true); playingUserStates.BindCollectionChanged(onPlayingUserStatesChanged, true);
@ -77,24 +81,6 @@ namespace osu.Game.Screens.Spectate
})); }));
} }
private Task<User[]> getAllUsers()
{
var userLookupTasks = new List<Task<User>>();
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<WeakReference<BeatmapSetInfo>> e) private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> e)
{ {
if (!e.NewValue.TryGetTarget(out var beatmapSet)) if (!e.NewValue.TryGetTarget(out var beatmapSet))