From 1d311a66805aad4c784ff0a51bc523e69019ccde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Dec 2020 19:11:44 +0100 Subject: [PATCH 1/6] Change PlayingUsers population logic to match expectations --- .../Multiplayer/StatefulMultiplayerClient.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index fcb0977f53..c15401e99d 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -133,6 +133,7 @@ namespace osu.Game.Online.Multiplayer apiRoom = null; Room = null; + PlayingUsers.Clear(); RoomUpdated?.Invoke(); }, false); @@ -302,8 +303,7 @@ namespace osu.Game.Online.Multiplayer Room.Users.Single(u => u.UserID == userId).State = state; - if (state != MultiplayerUserState.Playing) - PlayingUsers.Remove(userId); + updatePlayingUsers(userId, state); RoomUpdated?.Invoke(); }, false); @@ -337,8 +337,6 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - PlayingUsers.AddRange(Room.Users.Where(u => u.State == MultiplayerUserState.Playing).Select(u => u.UserID)); - MatchStarted?.Invoke(); }, false); @@ -454,5 +452,17 @@ namespace osu.Game.Online.Multiplayer apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity. apiRoom.Playlist.Add(playlistItem); } + + private void updatePlayingUsers(int userId, MultiplayerUserState state) + { + bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay; + bool wasPlaying = PlayingUsers.Contains(userId); + + if (!wasPlaying && isPlaying) + PlayingUsers.Add(userId); + + if (wasPlaying && !isPlaying) + PlayingUsers.Remove(userId); + } } } From a014d0ec18953854650c33263489273171d828f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Dec 2020 19:12:58 +0100 Subject: [PATCH 2/6] Use PlayingUsers when constructing player directly --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 58314c3774..ba0ed16cf4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -200,7 +200,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Debug.Assert(client.Room != null); - int[] userIds = client.Room.Users.Where(u => u.State >= MultiplayerUserState.WaitingForLoad).Select(u => u.UserID).ToArray(); + int[] userIds = client.PlayingUsers.ToArray(); StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds)); } From f7407347f78445a141c196fbcda5282f1ba14250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Dec 2020 20:01:17 +0100 Subject: [PATCH 3/6] Add test coverage of PlayingUsers tracking --- .../StatefulMultiplayerClientTest.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs new file mode 100644 index 0000000000..8d543e7485 --- /dev/null +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using Humanizer; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Online.Multiplayer; +using osu.Game.Tests.Visual.Multiplayer; +using osu.Game.Users; + +namespace osu.Game.Tests.NonVisual.Multiplayer +{ + [HeadlessTest] + public class StatefulMultiplayerClientTest : MultiplayerTestScene + { + [Test] + public void TestPlayingUserTracking() + { + int id = 2000; + + AddRepeatStep("add some users", () => Client.AddUser(new User { Id = id++ }), 5); + checkPlayingUserCount(0); + + changeState(3, MultiplayerUserState.WaitingForLoad); + checkPlayingUserCount(3); + + changeState(3, MultiplayerUserState.Playing); + checkPlayingUserCount(3); + + changeState(3, MultiplayerUserState.Results); + checkPlayingUserCount(0); + + changeState(6, MultiplayerUserState.WaitingForLoad); + checkPlayingUserCount(6); + + AddStep("another user left", () => Client.RemoveUser(Client.Room?.Users.Last().User)); + checkPlayingUserCount(5); + + AddStep("leave room", () => Client.LeaveRoom()); + checkPlayingUserCount(0); + } + + private void checkPlayingUserCount(int expectedCount) + => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.PlayingUsers.Count == expectedCount); + + private void changeState(int userCount, MultiplayerUserState state) + => AddStep($"{"user".ToQuantity(userCount)} in {state}", () => + { + for (int i = 0; i < userCount; ++i) + { + var userId = Client.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!"); + Client.ChangeUserState(userId, state); + } + }); + } +} From 6aeb7ece660ef5600f8b8da00ac38fd05a5def41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 14:25:08 +0900 Subject: [PATCH 4/6] Tidy up update state code, naming, xmldoc --- .../Multiplayer/StatefulMultiplayerClient.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index c15401e99d..bf62c450c9 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -303,7 +303,7 @@ namespace osu.Game.Online.Multiplayer Room.Users.Single(u => u.UserID == userId).State = state; - updatePlayingUsers(userId, state); + updateUserPlayingState(userId, state); RoomUpdated?.Invoke(); }, false); @@ -453,15 +453,22 @@ namespace osu.Game.Online.Multiplayer apiRoom.Playlist.Add(playlistItem); } - private void updatePlayingUsers(int userId, MultiplayerUserState state) + /// + /// For the provided user ID, update whether the user is included in . + /// + /// The user's ID. + /// The new state of the user. + private void updateUserPlayingState(int userId, MultiplayerUserState state) { - bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay; bool wasPlaying = PlayingUsers.Contains(userId); + bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay; - if (!wasPlaying && isPlaying) + if (isPlaying == wasPlaying) + return; + + if (isPlaying) PlayingUsers.Add(userId); - - if (wasPlaying && !isPlaying) + else PlayingUsers.Remove(userId); } } From e3a41f61186525d18d01bd8306e1d2a6f7a2baa3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 14:27:33 +0900 Subject: [PATCH 5/6] Rename variable to make more sense It needs to be explicitly stated that the users in this list are related to the *joined* room. Especially since it's sharing its variable name with `SpectatorStreamingClient` where it has the opposite meaning (is a list of *globally* playing players). --- .../Multiplayer/StatefulMultiplayerClientTest.cs | 2 +- .../TestSceneMultiplayerGameplayLeaderboard.cs | 6 +++--- .../Multiplayer/StatefulMultiplayerClient.cs | 16 ++++++++-------- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Play/HUD/MultiplayerGameplayLeaderboard.cs | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 8d543e7485..a2ad37cf4a 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer } private void checkPlayingUserCount(int expectedCount) - => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.PlayingUsers.Count == expectedCount); + => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.CurrentMatchPlayingUserIds.Count == expectedCount); private void changeState(int userCount, MultiplayerUserState state) => AddStep($"{"user".ToQuantity(userCount)} in {state}", () => diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index d0b1e77549..d016accc25 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -62,8 +62,8 @@ namespace osu.Game.Tests.Visual.Multiplayer streamingClient.Start(Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); - Client.PlayingUsers.Clear(); - Client.PlayingUsers.AddRange(streamingClient.PlayingUsers); + Client.CurrentMatchPlayingUserIds.Clear(); + Client.CurrentMatchPlayingUserIds.AddRange(streamingClient.PlayingUsers); Children = new Drawable[] { @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestUserQuit() { - AddRepeatStep("mark user quit", () => Client.PlayingUsers.RemoveAt(0), users); + AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users); } public class TestMultiplayerStreaming : SpectatorStreamingClient diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index bf62c450c9..bb690d786a 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -61,9 +61,9 @@ namespace osu.Game.Online.Multiplayer public MultiplayerRoom? Room { get; private set; } /// - /// The users currently in gameplay. + /// The users in the joined which are currently in gameplay. /// - public readonly BindableList PlayingUsers = new BindableList(); + public readonly BindableList CurrentMatchPlayingUserIds = new BindableList(); [Resolved] private UserLookupCache userLookupCache { get; set; } = null!; @@ -133,7 +133,7 @@ namespace osu.Game.Online.Multiplayer apiRoom = null; Room = null; - PlayingUsers.Clear(); + CurrentMatchPlayingUserIds.Clear(); RoomUpdated?.Invoke(); }, false); @@ -254,7 +254,7 @@ namespace osu.Game.Online.Multiplayer return; Room.Users.Remove(user); - PlayingUsers.Remove(user.UserID); + CurrentMatchPlayingUserIds.Remove(user.UserID); RoomUpdated?.Invoke(); }, false); @@ -454,22 +454,22 @@ namespace osu.Game.Online.Multiplayer } /// - /// For the provided user ID, update whether the user is included in . + /// For the provided user ID, update whether the user is included in . /// /// The user's ID. /// The new state of the user. private void updateUserPlayingState(int userId, MultiplayerUserState state) { - bool wasPlaying = PlayingUsers.Contains(userId); + bool wasPlaying = CurrentMatchPlayingUserIds.Contains(userId); bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay; if (isPlaying == wasPlaying) return; if (isPlaying) - PlayingUsers.Add(userId); + CurrentMatchPlayingUserIds.Add(userId); else - PlayingUsers.Remove(userId); + CurrentMatchPlayingUserIds.Remove(userId); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index ba0ed16cf4..ffa36ecfdb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -200,7 +200,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Debug.Assert(client.Room != null); - int[] userIds = client.PlayingUsers.ToArray(); + int[] userIds = client.CurrentMatchPlayingUserIds.ToArray(); StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds)); } diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index e7e5459f76..d4ce542a67 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -84,11 +84,11 @@ namespace osu.Game.Screens.Play.HUD // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. foreach (int userId in playingUsers) { - if (!multiplayerClient.PlayingUsers.Contains(userId)) + if (!multiplayerClient.CurrentMatchPlayingUserIds.Contains(userId)) usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId })); } - playingUsers.BindTo(multiplayerClient.PlayingUsers); + playingUsers.BindTo(multiplayerClient.CurrentMatchPlayingUserIds); playingUsers.BindCollectionChanged(usersChanged); } From f31a0e455a7834e839643a711df9dcd96409c315 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 14:29:40 +0900 Subject: [PATCH 6/6] Minor xmldoc rewording --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index bb690d786a..39d119b2a4 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -61,7 +61,7 @@ namespace osu.Game.Online.Multiplayer public MultiplayerRoom? Room { get; private set; } /// - /// The users in the joined which are currently in gameplay. + /// The users in the joined which are participating in the current gameplay loop. /// public readonly BindableList CurrentMatchPlayingUserIds = new BindableList();