From ad422295c85d257044edd33dba7284b7c8d9b631 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Jan 2025 21:38:37 +0900 Subject: [PATCH 01/13] Add ctor to create Rooms from MultiplayerRooms --- osu.Game/Online/Rooms/Room.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index f8660a656e6f..764713464644 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -342,6 +342,29 @@ public RoomAvailability Availability // Not yet serialised (not implemented). private RoomAvailability availability; + public Room() + { + } + + /// + /// Creates a from a . + /// + public Room(MultiplayerRoom room) + { + RoomID = room.RoomID; + Host = room.Host?.User; + + Name = room.Settings.Name; + Password = room.Settings.Password; + Type = room.Settings.MatchType; + QueueMode = room.Settings.QueueMode; + AutoStartDuration = room.Settings.AutoStartDuration; + AutoSkip = room.Settings.AutoSkip; + + Playlist = room.Playlist.Select(item => new PlaylistItem(item)).ToArray(); + CurrentPlaylistItem = Playlist.FirstOrDefault(item => item.ID == room.Settings.PlaylistItemId); + } + /// /// Copies values from another into this one. /// From 3d2d4ee89f06a88feabcfdda1b73ac1cbeaf1c49 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Jan 2025 22:07:13 +0900 Subject: [PATCH 02/13] Add ctor to create MultiplayerPlaylistItem from PlaylistItem --- osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index 8be703e620be..6e467c1d2652 100644 --- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -60,5 +60,20 @@ public class MultiplayerPlaylistItem public MultiplayerPlaylistItem() { } + + public MultiplayerPlaylistItem(PlaylistItem item) + { + ID = item.ID; + OwnerID = item.OwnerID; + BeatmapID = item.Beatmap.OnlineID; + BeatmapChecksum = item.Beatmap.MD5Hash; + RulesetID = item.RulesetID; + RequiredMods = item.RequiredMods.ToArray(); + AllowedMods = item.AllowedMods.ToArray(); + Expired = item.Expired; + PlaylistOrder = item.PlaylistOrder ?? 0; + PlayedAt = item.PlayedAt; + StarRating = item.Beatmap.StarRating; + } } } From ad28de8ae3aa1d2817fc8511929d52c2c3ab0b20 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 17 Jan 2025 21:44:40 +0900 Subject: [PATCH 03/13] Create multiplayer rooms via multiplayer server --- .../Multiplayer/IMultiplayerLoungeServer.cs | 2 + .../Online/Multiplayer/MultiplayerClient.cs | 40 +++++++++++----- .../Online/Multiplayer/MultiplayerRoom.cs | 9 ++++ .../Multiplayer/MultiplayerRoomSettings.cs | 14 ++++++ .../Multiplayer/OnlineMultiplayerClient.cs | 25 ++++++++++ osu.Game/Online/Rooms/Room.cs | 23 ---------- .../Match/MultiplayerMatchSettingsOverlay.cs | 46 +++++++++---------- .../Multiplayer/MultiplayerMatchSubScreen.cs | 5 +- .../Multiplayer/TestMultiplayerClient.cs | 5 ++ 9 files changed, 105 insertions(+), 64 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs index f266c38b8b23..c5eb6f9b36c6 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs @@ -10,6 +10,8 @@ namespace osu.Game.Online.Multiplayer /// public interface IMultiplayerLoungeServer { + Task CreateRoom(MultiplayerRoom room); + /// /// Request to join a multiplayer room. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 4a28124583bb..d0c3a1fa0687 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -165,6 +165,15 @@ private void load() private readonly TaskChain joinOrLeaveTaskChain = new TaskChain(); private CancellationTokenSource? joinCancellationSource; + public async Task CreateRoom(Room room) + { + if (Room != null) + throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); + + var cancellationSource = joinCancellationSource = new CancellationTokenSource(); + await initRoom(room, r => CreateRoom(new MultiplayerRoom(room)), cancellationSource.Token); + } + /// /// Joins the for a given API . /// @@ -175,34 +184,34 @@ public async Task JoinRoom(Room room, string? password = null) if (Room != null) throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); + Debug.Assert(room.RoomID != null); + var cancellationSource = joinCancellationSource = new CancellationTokenSource(); + await initRoom(room, r => JoinRoom(room.RoomID.Value, password ?? room.Password), cancellationSource.Token); + } + private async Task initRoom(Room room, Func> initFunc, CancellationToken cancellationToken) + { await joinOrLeaveTaskChain.Add(async () => { - Debug.Assert(room.RoomID != null); - - // Join the server-side room. - var joinedRoom = await JoinRoom(room.RoomID.Value, password ?? room.Password).ConfigureAwait(false); - Debug.Assert(joinedRoom != null); + // Initialise the server-side room. + MultiplayerRoom joinedRoom = await initFunc(room).ConfigureAwait(false); // Populate users. - Debug.Assert(joinedRoom.Users != null); await PopulateUsers(joinedRoom.Users).ConfigureAwait(false); // Update the stored room (must be done on update thread for thread-safety). await runOnUpdateThreadAsync(() => { Debug.Assert(Room == null); + Debug.Assert(APIRoom == null); Room = joinedRoom; APIRoom = room; - Debug.Assert(joinedRoom.Playlist.Count > 0); - + APIRoom.RoomID = joinedRoom.RoomID; APIRoom.Playlist = joinedRoom.Playlist.Select(item => new PlaylistItem(item)).ToArray(); APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); - - // The server will null out the end date upon the host joining the room, but the null value is never communicated to the client. APIRoom.EndDate = null; Debug.Assert(LocalUser != null); @@ -216,8 +225,8 @@ await runOnUpdateThreadAsync(() => postServerShuttingDownNotification(); OnRoomJoined(); - }, cancellationSource.Token).ConfigureAwait(false); - }, cancellationSource.Token).ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); } /// @@ -227,6 +236,13 @@ protected virtual void OnRoomJoined() { } + /// + /// Creates the with the given settings. + /// + /// The room. + /// The joined + protected abstract Task CreateRoom(MultiplayerRoom room); + /// /// Joins the with a given ID. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs index 00048fa93197..f7bd4490ff6a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using MessagePack; using Newtonsoft.Json; using osu.Game.Online.Rooms; @@ -65,6 +66,14 @@ public MultiplayerRoom(long roomId) RoomID = roomId; } + public MultiplayerRoom(Room room) + { + RoomID = room.RoomID ?? 0; + Settings = new MultiplayerRoomSettings(room); + Host = room.Host != null ? new MultiplayerRoomUser(room.Host.OnlineID) : null; + Playlist = room.Playlist.Select(p => new MultiplayerPlaylistItem(p)).ToArray(); + } + public override string ToString() => $"RoomID:{RoomID} Host:{Host?.UserID} Users:{Users.Count} State:{State} Settings: [{Settings}]"; } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index c73b02874ebd..c264ec1eefb6 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -35,6 +35,20 @@ public class MultiplayerRoomSettings : IEquatable [IgnoreMember] public bool AutoStartEnabled => AutoStartDuration != TimeSpan.Zero; + public MultiplayerRoomSettings() + { + } + + public MultiplayerRoomSettings(Room room) + { + Name = room.Name; + Password = room.Password ?? string.Empty; + MatchType = room.Type; + QueueMode = room.QueueMode; + AutoStartDuration = room.AutoStartDuration; + AutoSkip = room.AutoSkip; + } + public bool Equals(MultiplayerRoomSettings? other) { if (ReferenceEquals(this, other)) return true; diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 40436d730e3c..524873ef663b 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -266,6 +266,31 @@ public override Task RemovePlaylistItem(long playlistItemId) return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); } + protected override async Task CreateRoom(MultiplayerRoom room) + { + if (!IsConnected.Value) + throw new OperationCanceledException(); + + Debug.Assert(connection != null); + + try + { + return await connection.InvokeAsync(nameof(IMultiplayerServer.CreateRoom), room); + } + catch (HubException exception) + { + if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE) + { + Debug.Assert(connector != null); + + await connector.Reconnect().ConfigureAwait(false); + return await CreateRoom(room).ConfigureAwait(false); + } + + throw; + } + } + public override Task DisconnectInternal() { if (connector == null) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 764713464644..f8660a656e6f 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -342,29 +342,6 @@ public RoomAvailability Availability // Not yet serialised (not implemented). private RoomAvailability availability; - public Room() - { - } - - /// - /// Creates a from a . - /// - public Room(MultiplayerRoom room) - { - RoomID = room.RoomID; - Host = room.Host?.User; - - Name = room.Settings.Name; - Password = room.Settings.Password; - Type = room.Settings.MatchType; - QueueMode = room.Settings.QueueMode; - AutoStartDuration = room.Settings.AutoStartDuration; - AutoSkip = room.Settings.AutoSkip; - - Playlist = room.Playlist.Select(item => new PlaylistItem(item)).ToArray(); - CurrentPlaylistItem = Playlist.FirstOrDefault(item => item.ID == room.Settings.PlaylistItemId); - } - /// /// Copies values from another into this one. /// diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 137205414901..279b140d36c0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -29,12 +29,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public partial class MultiplayerMatchSettingsOverlay : RoomSettingsOverlay { - public required Bindable SelectedItem - { - get => selectedItem; - set => selectedItem.Current = value; - } - protected override OsuButton SubmitButton => settings.ApplyButton; protected override bool IsLoading => ongoingOperationTracker.InProgress.Value; @@ -56,7 +50,6 @@ public MultiplayerMatchSettingsOverlay(Room room) RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Y, SettingsApplied = Hide, - SelectedItem = { BindTarget = SelectedItem } }; protected partial class MatchSettings : CompositeDrawable @@ -65,7 +58,6 @@ protected partial class MatchSettings : CompositeDrawable public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - public readonly Bindable SelectedItem = new Bindable(); public Action? SettingsApplied; public OsuTextBox NameField = null!; @@ -86,9 +78,6 @@ protected partial class MatchSettings : CompositeDrawable [Resolved] private MultiplayerMatchSubScreen matchSubScreen { get; set; } = null!; - [Resolved] - private IRoomManager manager { get; set; } = null!; - [Resolved] private MultiplayerClient client { get; set; } = null!; @@ -279,7 +268,6 @@ private void load(OverlayColourProvider colourProvider, OsuColour colours) { RelativeSizeAxes = Axes.X, Height = DrawableRoomPlaylistItem.HEIGHT, - SelectedItem = { BindTarget = SelectedItem } }, selectBeatmapButton = new RoundedButton { @@ -482,19 +470,27 @@ private void apply() } else { - room.Name = NameField.Text; - room.Type = TypePicker.Current.Value; - room.Password = PasswordTextBox.Current.Value; - room.QueueMode = QueueModeDropdown.Current.Value; - room.AutoStartDuration = TimeSpan.FromSeconds((int)startModeDropdown.Current.Value); - room.AutoSkip = AutoSkipCheckbox.Current.Value; - - if (int.TryParse(MaxParticipantsField.Text, out int max)) - room.MaxParticipants = max; - else - room.MaxParticipants = null; + client.CreateRoom(room).ContinueWith(t => Schedule(() => + { + if (t.IsCompleted) + onSuccess(room); + else if (t.IsFaulted) + { + Exception? exception = t.Exception; + + if (exception is AggregateException ae) + exception = ae.InnerException; - manager.CreateRoom(room, onSuccess, onError); + Debug.Assert(exception != null); + + if (exception.GetHubExceptionMessage() is string message) + onError(message); + else + onError($"Error creating room: {exception}"); + } + else + onError("Error creating room."); + })); } } @@ -520,7 +516,7 @@ private void onError(string text) => Schedule(() => if (text.StartsWith(not_found_prefix, StringComparison.Ordinal)) { ErrorText.Text = "The selected beatmap is not available online."; - SelectedItem.Value?.MarkInvalid(); + room.Playlist.SingleOrDefault()?.MarkInvalid(); } else { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index edc45dbf7c49..06ea5ee033d1 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -233,10 +233,7 @@ internal void OpenSongSelection(PlaylistItem? itemToEdit = null) SelectedItem = SelectedItem }; - protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room) - { - SelectedItem = SelectedItem - }; + protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room); protected override void UpdateMods() { diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 4d812abf11f0..70e298f3e045 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -483,6 +483,11 @@ public async Task RemoveUserPlaylistItem(int userId, long playlistItemId) public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(playlistItemId)); + protected override Task CreateRoom(MultiplayerRoom room) + { + throw new NotImplementedException(); + } + private async Task changeMatchType(MatchType type) { Debug.Assert(ServerRoom != null); From 001d9cacf21cbe9dee9330b01b9e496e7be1f4f5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jan 2025 19:31:49 +0900 Subject: [PATCH 04/13] Configure awaiters --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 4 ++-- osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index d0c3a1fa0687..e5eade8c1da2 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -171,7 +171,7 @@ public async Task CreateRoom(Room room) throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); var cancellationSource = joinCancellationSource = new CancellationTokenSource(); - await initRoom(room, r => CreateRoom(new MultiplayerRoom(room)), cancellationSource.Token); + await initRoom(room, r => CreateRoom(new MultiplayerRoom(room)), cancellationSource.Token).ConfigureAwait(false); } /// @@ -187,7 +187,7 @@ public async Task JoinRoom(Room room, string? password = null) Debug.Assert(room.RoomID != null); var cancellationSource = joinCancellationSource = new CancellationTokenSource(); - await initRoom(room, r => JoinRoom(room.RoomID.Value, password ?? room.Password), cancellationSource.Token); + await initRoom(room, r => JoinRoom(room.RoomID.Value, password ?? room.Password), cancellationSource.Token).ConfigureAwait(false); } private async Task initRoom(Room room, Func> initFunc, CancellationToken cancellationToken) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 524873ef663b..05f3e444054b 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -275,7 +275,7 @@ protected override async Task CreateRoom(MultiplayerRoom room) try { - return await connection.InvokeAsync(nameof(IMultiplayerServer.CreateRoom), room); + return await connection.InvokeAsync(nameof(IMultiplayerServer.CreateRoom), room).ConfigureAwait(false); } catch (HubException exception) { From 02369baec43f0a68a26a960bef20980289b1f6ab Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 22 Jan 2025 21:44:45 +0900 Subject: [PATCH 05/13] Join/Leave rooms via multiplayer server Relevant functionality has been removed from `RoomManager` in the process. --- .../TestSceneMultiplayerLoungeSubScreen.cs | 26 ------ .../Online/Multiplayer/MultiplayerClient.cs | 3 + osu.Game/Online/Rooms/CreateRoomRequest.cs | 2 +- osu.Game/Online/Rooms/JoinRoomRequest.cs | 1 + .../OnlinePlay/Components/RoomManager.cs | 80 ------------------- .../DailyChallenge/DailyChallenge.cs | 10 +-- osu.Game/Screens/OnlinePlay/IRoomManager.cs | 22 ----- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 9 ++- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 6 +- .../OnlinePlay/Multiplayer/Multiplayer.cs | 3 - .../Multiplayer/MultiplayerLoungeSubScreen.cs | 34 ++++---- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 + .../Multiplayer/MultiplayerRoomManager.cs | 72 ----------------- .../Screens/OnlinePlay/OnlinePlayScreen.cs | 12 +-- .../Screens/OnlinePlay/OnlinePlaySubScreen.cs | 4 - .../Playlists/PlaylistsLoungeSubScreen.cs | 15 ++++ .../Playlists/PlaylistsRoomSettingsOverlay.cs | 9 ++- .../Playlists/PlaylistsRoomSubScreen.cs | 2 + .../Multiplayer/MultiplayerTestScene.cs | 2 +- .../Multiplayer/TestMultiplayerRoomManager.cs | 10 +-- .../Visual/OnlinePlay/TestRoomManager.cs | 13 ++- 21 files changed, 74 insertions(+), 263 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index 9951f62c77f6..d06a91433d1b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -8,7 +8,6 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Tests.Visual.OnlinePlay; @@ -21,23 +20,13 @@ public partial class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; private LoungeSubScreen loungeScreen = null!; - private Room? lastJoinedRoom; - private string? lastJoinedPassword; public override void SetUpSteps() { base.SetUpSteps(); AddStep("push screen", () => LoadScreen(loungeScreen = new MultiplayerLoungeSubScreen())); - AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen()); - - AddStep("bind to event", () => - { - lastJoinedRoom = null; - lastJoinedPassword = null; - RoomManager.JoinRoomRequested = onRoomJoined; - }); } [Test] @@ -46,9 +35,6 @@ public void TestJoinRoomWithoutPassword() AddStep("add room", () => RoomManager.AddRooms(1, withPassword: false)); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("join room", () => InputManager.Key(Key.Enter)); - - AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First()); - AddAssert("room join password correct", () => lastJoinedPassword == null); } [Test] @@ -126,9 +112,6 @@ public void TestJoinRoomWithCorrectPassword() AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password"); AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick()); - - AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First()); - AddAssert("room join password correct", () => lastJoinedPassword == "password"); } [Test] @@ -142,15 +125,6 @@ public void TestJoinRoomWithPasswordViaKeyboardOnly() AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password"); AddStep("press enter", () => InputManager.Key(Key.Enter)); - - AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First()); - AddAssert("room join password correct", () => lastJoinedPassword == "password"); - } - - private void onRoomJoined(Room room, string? password) - { - lastJoinedRoom = room; - lastJoinedPassword = password; } } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index e5eade8c1da2..7dfe974651ac 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -253,6 +253,9 @@ protected virtual void OnRoomJoined() public Task LeaveRoom() { + if (Room == null) + return Task.CompletedTask; + // The join may have not completed yet, so certain tasks that either update the room or reference the room should be cancelled. // This includes the setting of Room itself along with the initial update of the room settings on join. joinCancellationSource?.Cancel(); diff --git a/osu.Game/Online/Rooms/CreateRoomRequest.cs b/osu.Game/Online/Rooms/CreateRoomRequest.cs index 63a3b7bfa8b6..9773bb5e7d97 100644 --- a/osu.Game/Online/Rooms/CreateRoomRequest.cs +++ b/osu.Game/Online/Rooms/CreateRoomRequest.cs @@ -15,6 +15,7 @@ public class CreateRoomRequest : APIRequest public CreateRoomRequest(Room room) { Room = room; + Success += r => Room.CopyFrom(r); } protected override WebRequest CreateWebRequest() @@ -23,7 +24,6 @@ protected override WebRequest CreateWebRequest() req.ContentType = "application/json"; req.Method = HttpMethod.Post; - req.AddRaw(JsonConvert.SerializeObject(Room)); return req; diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs index dfc7a53fb239..13e7ac8c84a6 100644 --- a/osu.Game/Online/Rooms/JoinRoomRequest.cs +++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs @@ -16,6 +16,7 @@ public JoinRoomRequest(Room room, string? password) { Room = room; Password = password; + Success += r => Room.CopyFrom(r); } protected override WebRequest CreateWebRequest() diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 73f980f0a378..3abb4098fb9f 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -5,12 +5,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Logging; -using osu.Game.Online.API; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components @@ -23,89 +21,11 @@ public partial class RoomManager : Component, IRoomManager public IBindableList Rooms => rooms; - protected IBindable JoinedRoom => joinedRoom; - private readonly Bindable joinedRoom = new Bindable(); - - [Resolved] - private IAPIProvider api { get; set; } = null!; - public RoomManager() { RelativeSizeAxes = Axes.Both; } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - PartRoom(); - } - - public virtual void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) - { - room.Host = api.LocalUser.Value; - - var req = new CreateRoomRequest(room); - - req.Success += result => - { - joinedRoom.Value = room; - - AddOrUpdateRoom(result); - room.CopyFrom(result); // Also copy back to the source model, since this is likely to have been stored elsewhere. - - // The server may not contain all properties (such as password), so invoke success with the given room. - onSuccess?.Invoke(room); - }; - - req.Failure += exception => - { - onError?.Invoke(req.Response?.Error ?? exception.Message); - }; - - api.Queue(req); - } - - private JoinRoomRequest? currentJoinRoomRequest; - - public virtual void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) - { - currentJoinRoomRequest?.Cancel(); - currentJoinRoomRequest = new JoinRoomRequest(room, password); - - currentJoinRoomRequest.Success += result => - { - joinedRoom.Value = room; - - AddOrUpdateRoom(result); - room.CopyFrom(result); // Also copy back to the source model, since this is likely to have been stored elsewhere. - - onSuccess?.Invoke(room); - }; - - currentJoinRoomRequest.Failure += exception => - { - if (exception is OperationCanceledException) - return; - - onError?.Invoke(exception.Message); - }; - - api.Queue(currentJoinRoomRequest); - } - - public virtual void PartRoom() - { - currentJoinRoomRequest?.Cancel(); - - if (joinedRoom.Value == null) - return; - - if (api.State.Value == APIState.Online) - api.Queue(new PartRoomRequest(joinedRoom.Value)); - - joinedRoom.Value = null; - } - private readonly HashSet ignoredRooms = new HashSet(); public void AddOrUpdateRoom(Room room) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 13a282dd52ea..e3d6d42c055b 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -34,7 +34,6 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; @@ -71,9 +70,6 @@ public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner, IHandlePres [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); - [Cached(Type = typeof(IRoomManager))] - private RoomManager roomManager { get; set; } - [Cached] private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); @@ -115,7 +111,6 @@ public DailyChallenge(Room room) { this.room = room; playlistItem = room.Playlist.Single(); - roomManager = new RoomManager(); Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING }; } @@ -131,7 +126,6 @@ private void load(AudioManager audio) RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - roomManager, beatmapAvailabilityTracker, new ScreenStack(new RoomBackgroundScreen(playlistItem)) { @@ -426,7 +420,7 @@ public override void OnEntering(ScreenTransitionEvent e) base.OnEntering(e); waves.Show(); - roomManager.JoinRoom(room); + API.Queue(new JoinRoomRequest(room, null)); startLoopingTrack(this, musicController); metadataClient.BeginWatchingMultiplayerRoom(room.RoomID!.Value).ContinueWith(t => @@ -480,7 +474,7 @@ public override bool OnExiting(ScreenExitEvent e) previewTrackManager.StopAnyPlaying(this); this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); - roomManager.PartRoom(); + API.Queue(new PartRoomRequest(room)); metadataClient.EndWatchingMultiplayerRoom(room.RoomID!.Value).FireAndForget(); return base.OnExiting(e); diff --git a/osu.Game/Screens/OnlinePlay/IRoomManager.cs b/osu.Game/Screens/OnlinePlay/IRoomManager.cs index ed4fb7b15e69..8ecb1dd7e0e9 100644 --- a/osu.Game/Screens/OnlinePlay/IRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/IRoomManager.cs @@ -38,27 +38,5 @@ public interface IRoomManager /// Removes all s from this . /// void ClearRooms(); - - /// - /// Creates a new . - /// - /// The to create. - /// An action to be invoked if the creation succeeds. - /// An action to be invoked if an error occurred. - void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null); - - /// - /// Joins a . - /// - /// The to join. must be populated. - /// An optional password to use for the join operation. - /// - /// - void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null); - - /// - /// Parts the currently-joined . - /// - void PartRoom(); } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index f00cf7427c0a..f3f4df166a18 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -263,6 +263,9 @@ public override void OnResuming(ScreenTransitionEvent e) music.EnsurePlayingSomething(); onReturning(); + + // Poll for any newly-created rooms (including potentially the user's own). + ListingPollingComponent.PollImmediately(); } public override bool OnExiting(ScreenExitEvent e) @@ -297,14 +300,14 @@ private void onLeaving() popoverContainer.HidePopover(); } - public virtual void Join(Room room, string? password, Action? onSuccess = null, Action? onFailure = null) => Schedule(() => + public void Join(Room room, string? password, Action? onSuccess = null, Action? onFailure = null) => Schedule(() => { if (joiningRoomOperation != null) return; joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); - RoomManager?.JoinRoom(room, password, _ => + TryJoin(room, password, r => { Open(room); joiningRoomOperation?.Dispose(); @@ -318,6 +321,8 @@ public virtual void Join(Room room, string? password, Action? onSuccess = }); }); + protected abstract void TryJoin(Room room, string? password, Action onSuccess, Action onFailure); + /// /// Copies a room and opens it as a fresh (not-yet-created) one. /// diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 4ef31c02c3ac..d37f3b877c26 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -343,7 +343,9 @@ public override bool OnExiting(ScreenExitEvent e) if (!ensureExitConfirmed()) return true; - RoomManager?.PartRoom(); + if (Room.RoomID != null) + PartRoom(); + Mods.Value = Array.Empty(); onLeaving(); @@ -351,6 +353,8 @@ public override bool OnExiting(ScreenExitEvent e) return base.OnExiting(e); } + protected abstract void PartRoom(); + private bool ensureExitConfirmed() { if (ExitConfirmed) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index bf316bb3da5b..dfed32aebccd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -8,7 +8,6 @@ using osu.Framework.Screens; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge; namespace osu.Game.Screens.OnlinePlay.Multiplayer @@ -97,8 +96,6 @@ private void transitionFromResults() protected override string ScreenTitle => "Multiplayer"; - protected override RoomManager CreateRoomManager() => new MultiplayerRoomManager(); - protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen(); public void Join(Room room, string? password) => Schedule(() => Lounge.Join(room, password)); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index dd61caa3db37..e901ecbdce51 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -1,12 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Logging; -using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; @@ -32,19 +33,6 @@ public partial class MultiplayerLoungeSubScreen : LoungeSubScreen private Dropdown roomAccessTypeDropdown = null!; private OsuCheckbox showInProgress = null!; - public override void OnResuming(ScreenTransitionEvent e) - { - base.OnResuming(e); - - // Upon having left a room, we don't know whether we were the only participant, and whether the room is now closed as a result of leaving it. - // To work around this, temporarily remove the room and trigger an immediate listing poll. - if (e.Last is MultiplayerMatchSubScreen match) - { - RoomManager?.RemoveRoom(match.Room); - ListingPollingComponent.PollImmediately(); - } - } - protected override IEnumerable CreateFilterControls() { foreach (var control in base.CreateFilterControls()) @@ -93,6 +81,24 @@ protected override FilterCriteria CreateFilterCriteria() protected override ListingPollingComponent CreatePollingComponent() => new MultiplayerListingPollingComponent(); + protected override void TryJoin(Room room, string? password, Action onSuccess, Action onFailure) + { + client.JoinRoom(room, password).ContinueWith(result => + { + if (result.IsCompletedSuccessfully) + onSuccess(room); + else + { + const string message = "Failed to join multiplayer room."; + + if (result.Exception != null) + Logger.Error(result.Exception, message); + + onFailure.Invoke(result.Exception?.AsSingular().Message ?? message); + } + }); + } + protected override void OpenNewRoom(Room room) { if (!client.IsConnected.Value) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 06ea5ee033d1..553c0c9182e9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -278,6 +278,8 @@ public override bool OnExiting(ScreenExitEvent e) return base.OnExiting(e); } + protected override void PartRoom() => client.LeaveRoom(); + private ModSettingChangeTracker? modSettingChangeTracker; private ScheduledDelegate? debouncedModSettingsUpdate; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs deleted file mode 100644 index 7f09c9cbe972..000000000000 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Diagnostics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.ExceptionExtensions; -using osu.Framework.Logging; -using osu.Game.Online.Multiplayer; -using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay.Components; - -namespace osu.Game.Screens.OnlinePlay.Multiplayer -{ - public partial class MultiplayerRoomManager : RoomManager - { - [Resolved] - private MultiplayerClient multiplayerClient { get; set; } = null!; - - public override void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) - => base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password, onSuccess, onError), onError); - - public override void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) - { - if (!multiplayerClient.IsConnected.Value) - { - onError?.Invoke("Not currently connected to the multiplayer server."); - return; - } - - // this is done here as a pre-check to avoid clicking on already closed rooms in the lounge from triggering a server join. - // should probably be done at a higher level, but due to the current structure of things this is the easiest place for now. - if (room.HasEnded) - { - onError?.Invoke("Cannot join an ended room."); - return; - } - - base.JoinRoom(room, password, r => joinMultiplayerRoom(r, password, onSuccess, onError), onError); - } - - public override void PartRoom() - { - if (JoinedRoom.Value == null) - return; - - base.PartRoom(); - multiplayerClient.LeaveRoom(); - } - - private void joinMultiplayerRoom(Room room, string? password, Action? onSuccess = null, Action? onError = null) - { - Debug.Assert(room.RoomID != null); - - multiplayerClient.JoinRoom(room, password).ContinueWith(t => - { - if (t.IsCompletedSuccessfully) - Schedule(() => onSuccess?.Invoke(room)); - else if (t.IsFaulted) - { - const string message = "Failed to join multiplayer room."; - - if (t.Exception != null) - Logger.Error(t.Exception, message); - - PartRoom(); - Schedule(() => onError?.Invoke(t.Exception?.AsSingular().Message ?? message)); - } - }); - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 17fb667e1400..16462b90c121 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -36,12 +36,12 @@ public abstract partial class OnlinePlayScreen : OsuScreen, IHasSubScreenStack private readonly ScreenStack screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both }; private OnlinePlayScreenWaveContainer waves = null!; - [Cached(Type = typeof(IRoomManager))] - protected RoomManager RoomManager { get; private set; } - [Cached] private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker(); + [Cached(Type = typeof(IRoomManager))] + private readonly RoomManager roomManager = new RoomManager(); + [Resolved] protected IAPIProvider API { get; private set; } = null!; @@ -51,8 +51,6 @@ protected OnlinePlayScreen() Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING }; - - RoomManager = CreateRoomManager(); } private readonly IBindable apiState = new Bindable(); @@ -67,7 +65,7 @@ private void load() { screenStack, new Header(ScreenTitle, screenStack), - RoomManager, + roomManager, ongoingOperationTracker, } }; @@ -165,8 +163,6 @@ public override bool OnExiting(ScreenExitEvent e) subScreen.Exit(); } - RoomManager.PartRoom(); - waves.Hide(); this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs index fa1ee004c9bc..9b35a794a30a 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySubScreen.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; @@ -15,9 +14,6 @@ public abstract partial class OnlinePlaySubScreen : OsuScreen, IOnlinePlaySubScr protected sealed override bool PlayExitSound => false; - [Resolved] - protected IRoomManager? RoomManager { get; private set; } - protected OnlinePlaySubScreen() { Anchor = Anchor.Centre; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs index d66b4f844c48..92415e0eb180 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -59,6 +60,20 @@ protected override FilterCriteria CreateFilterCriteria() return criteria; } + protected override void TryJoin(Room room, string? password, Action onSuccess, Action onFailure) + { + var joinRoomRequest = new JoinRoomRequest(room, password); + + joinRoomRequest.Success += r => onSuccess(r); + joinRoomRequest.Failure += exception => + { + if (exception is not OperationCanceledException) + onFailure(exception.Message); + }; + + api.Queue(joinRoomRequest); + } + protected override OsuButton CreateNewRoomButton() => new CreatePlaylistsRoomButton(); protected override Room CreateNewRoom() diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 88af161cc847..b3d1d577ed67 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -75,9 +75,6 @@ protected partial class MatchSettings : CompositeDrawable private PurpleRoundedButton editPlaylistButton = null!; - [Resolved] - private IRoomManager? manager { get; set; } - [Resolved] private IAPIProvider api { get; set; } = null!; @@ -449,7 +446,11 @@ private void apply() room.Duration = DurationField.Current.Value; loadingLayer.Show(); - manager?.CreateRoom(room, onSuccess, onError); + + var req = new CreateRoomRequest(room); + req.Success += onSuccess; + req.Failure += e => onError(req.Response?.Error ?? e.Message); + api.Queue(req); } private void hideError() => ErrorText.FadeOut(50); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 9b4630ac0b09..064c355a69f4 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -290,6 +290,8 @@ private void closePlaylist() })); } + protected override void PartRoom() => api.Queue(new PartRoomRequest(Room)); + protected override Screen CreateGameplayScreen(PlaylistItem selectedItem) { return new PlayerLoader(() => new PlaylistsPlayer(Room, selectedItem) diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 42cf31782946..dca1fc8f3ca7 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -56,7 +56,7 @@ public override void SetUpSteps() AddStep("join room", () => { SelectedRoom.Value = CreateRoom(); - RoomManager.CreateRoom(SelectedRoom.Value); + API.Queue(new CreateRoomRequest(SelectedRoom.Value)); }); AddUntilStep("wait for room join", () => RoomJoined); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index b998a638e5b5..59ac9a97492f 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -1,12 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; -using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer @@ -15,7 +13,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// A for use in multiplayer test scenes. /// Should generally not be used by itself outside of a . /// - public partial class TestMultiplayerRoomManager : MultiplayerRoomManager + public partial class TestMultiplayerRoomManager : RoomManager { private readonly TestRoomRequestsHandler requestsHandler; @@ -26,12 +24,6 @@ public TestMultiplayerRoomManager(TestRoomRequestsHandler requestsHandler) public IReadOnlyList ServerSideRooms => requestsHandler.ServerSideRooms; - public override void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) - => base.CreateRoom(room, r => onSuccess?.Invoke(r), onError); - - public override void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) - => base.JoinRoom(room, password, r => onSuccess?.Invoke(r), onError); - /// /// Adds a room to a local "server-side" list that's returned when a is fired. /// diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index b1e3eafacc3e..60d169a46fd2 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Game.Beatmaps; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -15,15 +17,10 @@ namespace osu.Game.Tests.Visual.OnlinePlay /// public partial class TestRoomManager : RoomManager { - public Action? JoinRoomRequested; - private int currentRoomId; - public override void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null) - { - JoinRoomRequested?.Invoke(room, password); - base.JoinRoom(room, password, onSuccess, onError); - } + [Resolved] + private IAPIProvider api { get; set; } = null!; public void AddRooms(int count, RulesetInfo? ruleset = null, bool withPassword = false, bool withSpotlightRooms = false) { @@ -49,7 +46,7 @@ public void AddRooms(int count, RulesetInfo? ruleset = null, bool withPassword = public void AddRoom(Room room) { room.RoomID = -currentRoomId; - CreateRoom(room); + api.Queue(new CreateRoomRequest(room)); currentRoomId++; } } From 9a623257f5bd8cfed7f2d691fbb1c2959483c111 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 Jan 2025 16:19:09 +0900 Subject: [PATCH 06/13] Adjust + fix tests --- .../StatefulMultiplayerClientTest.cs | 7 +- .../TestSceneDrawableLoungeRoom.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 15 ++-- .../TestSceneMultiplayerLoungeSubScreen.cs | 58 +++++++++++--- .../TestSceneMultiplayerPlaylist.cs | 10 +-- .../TestScenePlaylistsLoungeSubScreen.cs | 30 ++++++- .../TestScenePlaylistsMatchSettingsOverlay.cs | 78 +++++++------------ .../Visual/TestMultiplayerComponents.cs | 24 ++---- osu.Game/Online/Rooms/Room.cs | 17 ++++ .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 14 +--- .../OnlinePlay/Lounge/IOnlinePlayLounge.cs | 32 ++++++++ .../OnlinePlay/Lounge/LoungeSubScreen.cs | 19 +++-- .../Screens/OnlinePlay/OnlinePlayScreen.cs | 2 - .../IMultiplayerTestSceneDependencies.cs | 6 -- .../Multiplayer/MultiplayerTestScene.cs | 3 +- .../MultiplayerTestSceneDependencies.cs | 6 +- .../Multiplayer/TestMultiplayerClient.cs | 35 +++++++-- .../Multiplayer/TestMultiplayerRoomManager.cs | 34 -------- .../OnlinePlayTestSceneDependencies.cs | 4 +- .../Visual/OnlinePlay/TestRoomManager.cs | 20 +++-- 20 files changed, 232 insertions(+), 184 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Lounge/IOnlinePlayLounge.cs delete mode 100644 osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 559db1675180..be30e06ed4b6 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -8,7 +8,6 @@ using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Rooms; using osu.Game.Tests.Visual.Multiplayer; namespace osu.Game.Tests.NonVisual.Multiplayer @@ -72,10 +71,6 @@ public void TestPlayingUsersUpdatedOnJoin() AddStep("create room initially in gameplay", () => { - var newRoom = new Room(); - newRoom.CopyFrom(SelectedRoom.Value!); - - newRoom.RoomID = null; MultiplayerClient.RoomSetupAction = room => { room.State = MultiplayerRoomState.Playing; @@ -86,7 +81,7 @@ public void TestPlayingUsersUpdatedOnJoin() }); }; - RoomManager.CreateRoom(newRoom); + MultiplayerClient.JoinRoom(MultiplayerClient.ServerSideRooms.Single()).ConfigureAwait(false); }); AddUntilStep("wait for room join", () => RoomJoined); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs index c5fb52461a34..459a90d0960e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs @@ -38,7 +38,7 @@ public partial class TestSceneDrawableLoungeRoom : OsuManualInputManagerTestScen [BackgroundDependencyLoader] private void load() { - var mockLounge = new Mock(); + var mockLounge = new Mock(); mockLounge .Setup(l => l.Join(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>())) .Callback, Action>((_, _, _, d) => diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index fb653cea8bd1..0966c61a3aa6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -58,7 +58,6 @@ public partial class TestSceneMultiplayer : ScreenTestScene private TestMultiplayerComponents multiplayerComponents = null!; private TestMultiplayerClient multiplayerClient => multiplayerComponents.MultiplayerClient; - private TestMultiplayerRoomManager roomManager => multiplayerComponents.RoomManager; [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -257,7 +256,7 @@ public void TestExitMidJoin() { AddStep("create room", () => { - roomManager.AddServerSideRoom(new Room + multiplayerClient.AddServerSideRoom(new Room { Name = "Test Room", Playlist = @@ -286,7 +285,7 @@ public void TestJoinRoomWithoutPassword() { AddStep("create room", () => { - roomManager.AddServerSideRoom(new Room + multiplayerClient.AddServerSideRoom(new Room { Name = "Test Room", Playlist = @@ -336,7 +335,7 @@ public void TestJoinRoomWithPassword() { AddStep("create room", () => { - roomManager.AddServerSideRoom(new Room + multiplayerClient.AddServerSideRoom(new Room { Name = "Test Room", Password = "password", @@ -789,7 +788,7 @@ public void TestRoomSettingsReQueriedWhenJoiningRoom() { AddStep("create room", () => { - roomManager.AddServerSideRoom(new Room + multiplayerClient.AddServerSideRoom(new Room { Name = "Test Room", QueueMode = QueueMode.AllPlayers, @@ -810,8 +809,8 @@ public void TestRoomSettingsReQueriedWhenJoiningRoom() AddStep("disable polling", () => this.ChildrenOfType().Single().TimeBetweenPolls.Value = 0); AddStep("change server-side settings", () => { - roomManager.ServerSideRooms[0].Name = "New name"; - roomManager.ServerSideRooms[0].Playlist = + multiplayerClient.ServerSideRooms[0].Name = "New name"; + multiplayerClient.ServerSideRooms[0].Playlist = [ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { @@ -828,7 +827,7 @@ public void TestRoomSettingsReQueriedWhenJoiningRoom() AddAssert("local room has correct settings", () => { var localRoom = this.ChildrenOfType().Single().Room; - return localRoom.Name == roomManager.ServerSideRooms[0].Name && localRoom.Playlist.Single().ID == 2; + return localRoom.Name == multiplayerClient.ServerSideRooms[0].Name && localRoom.Playlist.Single().ID == 2; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index d06a91433d1b..4a259149e254 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -9,18 +9,26 @@ using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.OnlinePlay.Lounge; +using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public partial class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene + public partial class TestSceneMultiplayerLoungeSubScreen : MultiplayerTestScene { protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; private LoungeSubScreen loungeScreen = null!; + private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType().First(); + + public TestSceneMultiplayerLoungeSubScreen() + : base(false) + { + } + public override void SetUpSteps() { base.SetUpSteps(); @@ -32,15 +40,17 @@ public override void SetUpSteps() [Test] public void TestJoinRoomWithoutPassword() { - AddStep("add room", () => RoomManager.AddRooms(1, withPassword: false)); + addRoom(false); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("join room", () => InputManager.Key(Key.Enter)); + + AddAssert("room joined", () => MultiplayerClient.RoomJoined); } [Test] public void TestPopoverHidesOnBackButton() { - AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + addRoom(true); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); @@ -53,18 +63,22 @@ public void TestPopoverHidesOnBackButton() AddStep("hit escape", () => InputManager.Key(Key.Escape)); AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType().Any()); + + AddAssert("room not joined", () => !MultiplayerClient.RoomJoined); } [Test] public void TestPopoverHidesOnLeavingScreen() { - AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + addRoom(true); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType().Any()); AddStep("exit screen", () => Stack.Exit()); AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType().Any()); + + AddAssert("room not joined", () => !MultiplayerClient.RoomJoined); } [Test] @@ -72,16 +86,18 @@ public void TestJoinRoomWithIncorrectPasswordViaButton() { DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; - AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + addRoom(true); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "wrong"); AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick()); - AddAssert("room not joined", () => loungeScreen.IsCurrentScreen()); + AddAssert("still at lounge", () => loungeScreen.IsCurrentScreen()); AddUntilStep("password prompt still visible", () => passwordEntryPopover!.State.Value == Visibility.Visible); AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox); + + AddAssert("room not joined", () => !MultiplayerClient.RoomJoined); } [Test] @@ -89,16 +105,18 @@ public void TestJoinRoomWithIncorrectPasswordViaEnter() { DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; - AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + addRoom(true); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "wrong"); AddStep("press enter", () => InputManager.Key(Key.Enter)); - AddAssert("room not joined", () => loungeScreen.IsCurrentScreen()); + AddAssert("still at lounge", () => loungeScreen.IsCurrentScreen()); AddUntilStep("password prompt still visible", () => passwordEntryPopover!.State.Value == Visibility.Visible); AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox); + + AddAssert("room not joined", () => !MultiplayerClient.RoomJoined); } [Test] @@ -106,12 +124,14 @@ public void TestJoinRoomWithCorrectPassword() { DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; - AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + addRoom(true); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password"); AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick()); + + AddUntilStep("room joined", () => MultiplayerClient.RoomJoined); } [Test] @@ -119,12 +139,30 @@ public void TestJoinRoomWithPasswordViaKeyboardOnly() { DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; - AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); + addRoom(true); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password"); AddStep("press enter", () => InputManager.Key(Key.Enter)); + + AddAssert("room joined", () => MultiplayerClient.RoomJoined); + } + + private void addRoom(bool withPassword) + { + int initialRoomCount = 0; + + AddStep("add room", () => + { + initialRoomCount = roomsContainer.Rooms.Count; + RoomManager.AddRooms(1, withPassword: withPassword); + loungeScreen.RefreshRooms(); + }); + + AddUntilStep("wait for room to appear", () => roomsContainer.Rooms.Count == initialRoomCount + 1); } + + protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new MultiplayerTestSceneDependencies(); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 36f5bba38436..77b75f407b33 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -127,7 +127,7 @@ public void TestListsClearedWhenRoomLeft() addItemStep(); AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely()); - AddStep("leave room", () => RoomManager.PartRoom()); + AddStep("leave room", () => MultiplayerClient.LeaveRoom()); AddUntilStep("wait for room part", () => !RoomJoined); AddUntilStep("item 0 not in lists", () => !inHistoryList(0) && !inQueueList(0)); @@ -148,7 +148,7 @@ public void TestQueueTabCount() AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely()); assertQueueTabCount(2); - AddStep("leave room", () => RoomManager.PartRoom()); + AddStep("leave room", () => MultiplayerClient.LeaveRoom()); AddUntilStep("wait for room part", () => !RoomJoined); assertQueueTabCount(0); } @@ -157,12 +157,12 @@ public void TestQueueTabCount() [Test] public void TestJoinRoomWithMixedItemsAddedInCorrectLists() { - AddStep("leave room", () => RoomManager.PartRoom()); + AddStep("leave room", () => MultiplayerClient.LeaveRoom()); AddUntilStep("wait for room part", () => !RoomJoined); AddStep("join room with items", () => { - RoomManager.CreateRoom(new Room + API.Queue(new CreateRoomRequest(new Room { Name = "test name", Playlist = @@ -177,7 +177,7 @@ public void TestJoinRoomWithMixedItemsAddedInCorrectLists() Expired = true } ] - }); + })); }); AddUntilStep("wait for room join", () => RoomJoined); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 8c8dc8d69ae6..0897a3b2f568 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -35,7 +35,13 @@ public override void SetUpSteps() [Test] public void TestManyRooms() { - AddStep("add rooms", () => RoomManager.AddRooms(500)); + AddStep("add rooms", () => + { + RoomManager.AddRooms(500); + loungeScreen.RefreshRooms(); + }); + + AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 500); } [Test] @@ -43,7 +49,12 @@ public void TestScrollByDraggingRooms() { AddStep("reset mouse", () => InputManager.ReleaseButton(MouseButton.Left)); - AddStep("add rooms", () => RoomManager.AddRooms(30)); + AddStep("add rooms", () => + { + RoomManager.AddRooms(30); + loungeScreen.RefreshRooms(); + }); + AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30); AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0])); @@ -60,7 +71,12 @@ public void TestScrollByDraggingRooms() [Test] public void TestScrollSelectedIntoView() { - AddStep("add rooms", () => RoomManager.AddRooms(30)); + AddStep("add rooms", () => + { + RoomManager.AddRooms(30); + loungeScreen.RefreshRooms(); + }); + AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30); AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0])); @@ -74,7 +90,13 @@ public void TestScrollSelectedIntoView() [Test] public void TestEnteringRoomTakesLeaseOnSelection() { - AddStep("add rooms", () => RoomManager.AddRooms(1)); + AddStep("add rooms", () => + { + RoomManager.AddRooms(1); + loungeScreen.RefreshRooms(); + }); + + AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 1); AddAssert("selected room is not disabled", () => !loungeScreen.SelectedRoom.Disabled); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index 58683314518e..51e39e1b7f95 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -3,14 +3,13 @@ using System; using NUnit.Framework; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Online.API; using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Tests.Visual.OnlinePlay; @@ -21,13 +20,33 @@ public partial class TestScenePlaylistsMatchSettingsOverlay : OnlinePlayTestScen protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; private TestRoomSettings settings = null!; - - protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); + private Func? handleRequest; public override void SetUpSteps() { base.SetUpSteps(); + AddStep("setup api", () => + { + handleRequest = null; + ((DummyAPIAccess)API).HandleRequest = req => + { + if (req is not CreateRoomRequest createReq || handleRequest == null) + return false; + + if (handleRequest(createReq.Room) is string errorText) + createReq.TriggerFailure(new APIException(errorText, null)); + else + { + var createdRoom = new APICreatedRoom(); + createdRoom.CopyFrom(createReq.Room); + createReq.TriggerSuccess(createdRoom); + } + + return true; + }; + }); + AddStep("create overlay", () => { SelectedRoom.Value = new Room(); @@ -75,10 +94,10 @@ public void TestCorrectSettingsApplied() settings.DurationField.Current.Value = expectedDuration; SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; - RoomManager.CreateRequested = r => + handleRequest = r => { createdRoom = r; - return string.Empty; + return null; }; }); @@ -103,7 +122,7 @@ public void TestInvalidBeatmapError() errorMessage = $"{not_found_prefix} {beatmap.OnlineID}"; - RoomManager.CreateRequested = _ => errorMessage; + handleRequest = _ => errorMessage; }); AddAssert("error not displayed", () => !settings.ErrorText.IsPresent); @@ -128,7 +147,7 @@ public void TestCreationFailureDisplaysError() SelectedRoom.Value!.Name = "Test Room"; SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]; - RoomManager.CreateRequested = _ => failText; + handleRequest = _ => failText; }); AddAssert("error not displayed", () => !settings.ErrorText.IsPresent); @@ -159,48 +178,5 @@ public TestRoomSettings(Room room) { } } - - private class TestDependencies : OnlinePlayTestSceneDependencies - { - protected override IRoomManager CreateRoomManager() => new TestRoomManager(); - } - - protected class TestRoomManager : IRoomManager - { - public Func? CreateRequested; - - public event Action RoomsUpdated - { - add { } - remove { } - } - - public IBindable InitialRoomsReceived { get; } = new Bindable(true); - - public IBindableList Rooms => null!; - - public void AddOrUpdateRoom(Room room) => throw new NotImplementedException(); - - public void RemoveRoom(Room room) => throw new NotImplementedException(); - - public void ClearRooms() => throw new NotImplementedException(); - - public void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null) - { - if (CreateRequested == null) - return; - - string error = CreateRequested.Invoke(room); - - if (!string.IsNullOrEmpty(error)) - onError?.Invoke(error); - else - onSuccess?.Invoke(room); - } - - public void JoinRoom(Room room, string? password, Action? onSuccess = null, Action? onError = null) => throw new NotImplementedException(); - - public void PartRoom() => throw new NotImplementedException(); - } } } diff --git a/osu.Game.Tests/Visual/TestMultiplayerComponents.cs b/osu.Game.Tests/Visual/TestMultiplayerComponents.cs index 1814fb70c8a6..e385ff3a03a0 100644 --- a/osu.Game.Tests/Visual/TestMultiplayerComponents.cs +++ b/osu.Game.Tests/Visual/TestMultiplayerComponents.cs @@ -11,7 +11,6 @@ using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Screens; -using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.OnlinePlay; @@ -26,15 +25,12 @@ namespace osu.Game.Tests.Visual /// Provides a to be resolved as a dependency in the screen, /// which is typically a part of . /// Rebinds the to handle requests via a . - /// Provides a for the screen. /// ///

/// public partial class TestMultiplayerComponents : OsuScreen { - public Screens.OnlinePlay.Multiplayer.Multiplayer MultiplayerScreen => multiplayerScreen; - - public TestMultiplayerRoomManager RoomManager => multiplayerScreen.RoomManager; + public Screens.OnlinePlay.Multiplayer.Multiplayer MultiplayerScreen { get; } public IScreen CurrentScreen => screenStack.CurrentScreen; @@ -53,17 +49,17 @@ public partial class TestMultiplayerComponents : OsuScreen private BeatmapManager beatmapManager { get; set; } private readonly OsuScreenStack screenStack; - private readonly TestMultiplayer multiplayerScreen; + private readonly TestRoomRequestsHandler requestsHandler = new TestRoomRequestsHandler(); public TestMultiplayerComponents() { - multiplayerScreen = new TestMultiplayer(); + MultiplayerScreen = new Screens.OnlinePlay.Multiplayer.Multiplayer(); InternalChildren = new Drawable[] { userLookupCache, beatmapLookupCache, - MultiplayerClient = new TestMultiplayerClient(RoomManager), + MultiplayerClient = new TestMultiplayerClient(requestsHandler), screenStack = new OsuScreenStack { Name = nameof(TestMultiplayerComponents), @@ -71,13 +67,13 @@ public TestMultiplayerComponents() } }; - screenStack.Push(multiplayerScreen); + screenStack.Push(MultiplayerScreen); } [BackgroundDependencyLoader] private void load(IAPIProvider api) { - ((DummyAPIAccess)api).HandleRequest = request => multiplayerScreen.RequestsHandler.HandleRequest(request, api.LocalUser.Value, beatmapManager); + ((DummyAPIAccess)api).HandleRequest = request => requestsHandler.HandleRequest(request, api.LocalUser.Value, beatmapManager); } public override bool OnBackButton() => (screenStack.CurrentScreen as OsuScreen)?.OnBackButton() ?? base.OnBackButton(); @@ -90,13 +86,5 @@ public override bool OnExiting(ScreenExitEvent e) screenStack.Exit(); return true; } - - private partial class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer - { - public new TestMultiplayerRoomManager RoomManager { get; private set; } - public TestRoomRequestsHandler RequestsHandler { get; private set; } - - protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager(RequestsHandler = new TestRoomRequestsHandler()); - } } } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index f8660a656e6f..c5e292a19d35 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -342,6 +342,23 @@ public RoomAvailability Availability // Not yet serialised (not implemented). private RoomAvailability availability; + public Room() + { + } + + public Room(MultiplayerRoom room) + { + RoomID = room.RoomID; + Name = room.Settings.Name; + Password = room.Settings.Password; + Type = room.Settings.MatchType; + QueueMode = room.Settings.QueueMode; + AutoStartDuration = room.Settings.AutoStartDuration; + AutoSkip = room.Settings.AutoSkip; + Host = room.Host != null ? new APIUser { Id = room.Host.UserID } : null; + Playlist = room.Playlist.Select(p => new PlaylistItem(p)).ToArray(); + } + /// /// Copies values from another into this one. /// diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 0a55472c2d75..032a231ad3f7 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -24,7 +24,6 @@ using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Components; @@ -51,7 +50,7 @@ public required Bindable SelectedRoom } [Resolved(canBeNull: true)] - private LoungeSubScreen? lounge { get; set; } + private IOnlinePlayLounge? lounge { get; set; } [Resolved] private IDialogOverlay? dialogOverlay { get; set; } @@ -163,7 +162,7 @@ public MenuItem[] ContextMenuItems { new OsuMenuItem("Create copy", MenuItemType.Standard, () => { - lounge?.OpenCopy(Room); + lounge?.Clone(Room); }) }; @@ -171,12 +170,7 @@ public MenuItem[] ContextMenuItems { items.Add(new OsuMenuItem("Close playlist", MenuItemType.Destructive, () => { - dialogOverlay?.Push(new ClosePlaylistDialog(Room, () => - { - var request = new ClosePlaylistRequest(Room.RoomID!.Value); - request.Success += () => lounge?.RefreshRooms(); - api.Queue(request); - })); + dialogOverlay?.Push(new ClosePlaylistDialog(Room, () => lounge?.Close(Room))); })); } @@ -239,7 +233,7 @@ public partial class PasswordEntryPopover : OsuPopover private readonly Room room; [Resolved(canBeNull: true)] - private LoungeSubScreen? lounge { get; set; } + private IOnlinePlayLounge? lounge { get; set; } public override bool HandleNonPositionalInput => true; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/IOnlinePlayLounge.cs b/osu.Game/Screens/OnlinePlay/Lounge/IOnlinePlayLounge.cs new file mode 100644 index 000000000000..8fa7d0751f0f --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Lounge/IOnlinePlayLounge.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Online.Rooms; + +namespace osu.Game.Screens.OnlinePlay.Lounge +{ + public interface IOnlinePlayLounge + { + /// + /// Attempts to join the given room. + /// + /// The room to join. + /// The password. + /// A delegate to invoke if the user joined the room. + /// A delegate to invoke if the user is not able join the room. + void Join(Room room, string? password, Action? onSuccess = null, Action? onFailure = null); + + /// + /// Clones the given room and opens it as a fresh (not-yet-created) one. + /// + /// The room to clone. + void Clone(Room room); + + /// + /// Closes the given room. + /// + /// The room to close. + void Close(Room room); + } +} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index f3f4df166a18..df17063fdf79 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -33,7 +34,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { [Cached] - public abstract partial class LoungeSubScreen : OnlinePlaySubScreen + [Cached(typeof(IOnlinePlayLounge))] + public abstract partial class LoungeSubScreen : OnlinePlaySubScreen, IOnlinePlayLounge { public override string Title => "Lounge"; @@ -323,11 +325,7 @@ public void Join(Room room, string? password, Action? onSuccess = null, Ac protected abstract void TryJoin(Room room, string? password, Action onSuccess, Action onFailure); - /// - /// Copies a room and opens it as a fresh (not-yet-created) one. - /// - /// The room to copy. - public void OpenCopy(Room room) + public void Clone(Room room) { Debug.Assert(room.RoomID != null); @@ -363,6 +361,15 @@ public void OpenCopy(Room room) api.Queue(req); } + public void Close(Room room) + { + Debug.Assert(room.RoomID != null); + + var request = new ClosePlaylistRequest(room.RoomID.Value); + request.Success += RefreshRooms; + api.Queue(request); + } + /// /// Push a room as a new subscreen. /// diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 16462b90c121..8988c82deed3 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -220,8 +220,6 @@ private void subScreenChanged(IScreen lastScreen, IScreen newScreen) protected abstract string ScreenTitle { get; } - protected virtual RoomManager CreateRoomManager() => new RoomManager(); - protected abstract LoungeSubScreen CreateLounge(); ScreenStack IHasSubScreenStack.SubScreenStack => screenStack; diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs index efd0b80ebf96..262816ae89b7 100644 --- a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; @@ -17,11 +16,6 @@ public interface IMultiplayerTestSceneDependencies : IOnlinePlayTestSceneDepende /// TestMultiplayerClient MultiplayerClient { get; } - /// - /// The cached . - /// - new TestMultiplayerRoomManager RoomManager { get; } - /// /// The cached . /// diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index dca1fc8f3ca7..d1497d5142ce 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -17,7 +17,6 @@ public abstract partial class MultiplayerTestScene : OnlinePlayTestScene, IMulti public const int PLAYER_2_ID = 56; public TestMultiplayerClient MultiplayerClient => OnlinePlayDependencies.MultiplayerClient; - public new TestMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager; public TestSpectatorClient SpectatorClient => OnlinePlayDependencies.SpectatorClient; protected new MultiplayerTestSceneDependencies OnlinePlayDependencies => (MultiplayerTestSceneDependencies)base.OnlinePlayDependencies; @@ -56,7 +55,7 @@ public override void SetUpSteps() AddStep("join room", () => { SelectedRoom.Value = CreateRoom(); - API.Queue(new CreateRoomRequest(SelectedRoom.Value)); + MultiplayerClient.CreateRoom(SelectedRoom.Value).ConfigureAwait(false); }); AddUntilStep("wait for room join", () => RoomJoined); diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs index 88202d432764..24c33f2f49f5 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs @@ -3,7 +3,6 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; -using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.Spectator; @@ -16,19 +15,16 @@ public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, { public TestMultiplayerClient MultiplayerClient { get; } public TestSpectatorClient SpectatorClient { get; } - public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager; public MultiplayerTestSceneDependencies() { - MultiplayerClient = new TestMultiplayerClient(RoomManager); + MultiplayerClient = new TestMultiplayerClient(RequestsHandler); SpectatorClient = CreateSpectatorClient(); CacheAs(MultiplayerClient); CacheAs(SpectatorClient); } - protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(RequestsHandler); - protected virtual TestSpectatorClient CreateSpectatorClient() => new TestSpectatorClient(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 70e298f3e045..d514fc0d7e7c 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Game.Beatmaps; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -17,6 +18,7 @@ using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Mods; +using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Multiplayer { @@ -65,15 +67,15 @@ public partial class TestMultiplayerClient : MultiplayerClient [Resolved] private IAPIProvider api { get; set; } = null!; - private readonly TestMultiplayerRoomManager roomManager; - private MultiplayerPlaylistItem? currentItem => ServerRoom?.Playlist[currentIndex]; private int currentIndex; private long lastPlaylistItemId; - public TestMultiplayerClient(TestMultiplayerRoomManager roomManager) + private readonly TestRoomRequestsHandler apiRequestHandler; + + public TestMultiplayerClient(TestRoomRequestsHandler? apiRequestHandler = null) { - this.roomManager = roomManager; + this.apiRequestHandler = apiRequestHandler ?? new TestRoomRequestsHandler(); } public void Connect() => isConnected.Value = true; @@ -214,7 +216,7 @@ protected override async Task JoinRoom(long roomId, string? pas roomId = clone(roomId); password = clone(password); - ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID == roomId); + ServerAPIRoom = ServerSideRooms.Single(r => r.RoomID == roomId); if (password != ServerAPIRoom.Password) throw new InvalidOperationException("Invalid password."); @@ -485,7 +487,15 @@ public async Task RemoveUserPlaylistItem(int userId, long playlistItemId) protected override Task CreateRoom(MultiplayerRoom room) { - throw new NotImplementedException(); + Room apiRoom = new Room(room) + { + Type = room.Settings.MatchType == MatchType.Playlists + ? MatchType.HeadToHead + : room.Settings.MatchType + }; + + AddServerSideRoom(apiRoom, api.LocalUser.Value); + return JoinRoom(apiRoom.RoomID!.Value, room.Settings.Password); } private async Task changeMatchType(MatchType type) @@ -680,5 +690,18 @@ public override Task DisconnectInternal() isConnected.Value = false; return Task.CompletedTask; } + + #region API Room Handling + + public IReadOnlyList ServerSideRooms + => apiRequestHandler.ServerSideRooms; + + public void AddServerSideRoom(Room room, APIUser host) + => apiRequestHandler.AddServerSideRoom(room, host); + + public bool HandleRequest(APIRequest request, APIUser localUser, BeatmapManager beatmapManager) + => apiRequestHandler.HandleRequest(request, localUser, beatmapManager); + + #endregion } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs deleted file mode 100644 index 59ac9a97492f..000000000000 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ /dev/null @@ -1,34 +0,0 @@ -// 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 osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay.Components; -using osu.Game.Tests.Visual.OnlinePlay; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - /// - /// A for use in multiplayer test scenes. - /// Should generally not be used by itself outside of a . - /// - public partial class TestMultiplayerRoomManager : RoomManager - { - private readonly TestRoomRequestsHandler requestsHandler; - - public TestMultiplayerRoomManager(TestRoomRequestsHandler requestsHandler) - { - this.requestsHandler = requestsHandler; - } - - public IReadOnlyList ServerSideRooms => requestsHandler.ServerSideRooms; - - /// - /// Adds a room to a local "server-side" list that's returned when a is fired. - /// - /// The room. - /// The host. - public void AddServerSideRoom(Room room, APIUser host) => requestsHandler.AddServerSideRoom(room, host); - } -} diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs index e2670c9ad890..203922c0576f 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs @@ -40,7 +40,7 @@ public OnlinePlayTestSceneDependencies() RequestsHandler = new TestRoomRequestsHandler(); OngoingOperationTracker = new OngoingOperationTracker(); AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); - RoomManager = CreateRoomManager(); + RoomManager = new TestRoomManager(); UserLookupCache = new TestUserLookupCache(); BeatmapLookupCache = new BeatmapLookupCache(); @@ -80,7 +80,5 @@ protected void CacheAs(T instance) if (instance is Drawable drawable) drawableComponents.Add(drawable); } - - protected virtual IRoomManager CreateRoomManager() => new TestRoomManager(); } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs index 60d169a46fd2..bff275392946 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs @@ -22,8 +22,14 @@ public partial class TestRoomManager : RoomManager [Resolved] private IAPIProvider api { get; set; } = null!; + [Resolved] + private RulesetStore rulesets { get; set; } = null!; + public void AddRooms(int count, RulesetInfo? ruleset = null, bool withPassword = false, bool withSpotlightRooms = false) { + // Can't reference Osu ruleset project here. + ruleset ??= rulesets.GetRuleset(0)!; + for (int i = 0; i < count; i++) { AddRoom(new Room @@ -33,12 +39,8 @@ public void AddRooms(int count, RulesetInfo? ruleset = null, bool withPassword = Duration = TimeSpan.FromSeconds(10), Category = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal, Password = withPassword ? @"password" : null, - PlaylistItemStats = ruleset == null - ? null - : new Room.RoomPlaylistItemStats { RulesetIDs = [ruleset.OnlineID] }, - Playlist = ruleset == null - ? Array.Empty() - : [new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) { RulesetID = ruleset.OnlineID }] + PlaylistItemStats = new Room.RoomPlaylistItemStats { RulesetIDs = [ruleset.OnlineID] }, + Playlist = [new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) { RulesetID = ruleset.OnlineID }] }); } } @@ -46,7 +48,11 @@ public void AddRooms(int count, RulesetInfo? ruleset = null, bool withPassword = public void AddRoom(Room room) { room.RoomID = -currentRoomId; - api.Queue(new CreateRoomRequest(room)); + + var req = new CreateRoomRequest(room); + req.Success += AddOrUpdateRoom; + api.Queue(req); + currentRoomId++; } } From 7c38089c7559350de5080cdad9b55d0e5165d41b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 Jan 2025 16:22:52 +0900 Subject: [PATCH 07/13] Rename methods --- .../Online/Multiplayer/MultiplayerClient.cs | 37 +++++++------ .../Multiplayer/OnlineMultiplayerClient.cs | 54 +++++++++---------- .../Multiplayer/TestMultiplayerClient.cs | 6 +-- 3 files changed, 50 insertions(+), 47 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 7dfe974651ac..a8f314d37292 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -171,7 +171,7 @@ public async Task CreateRoom(Room room) throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); var cancellationSource = joinCancellationSource = new CancellationTokenSource(); - await initRoom(room, r => CreateRoom(new MultiplayerRoom(room)), cancellationSource.Token).ConfigureAwait(false); + await initRoom(room, r => CreateRoomInternal(new MultiplayerRoom(room)), cancellationSource.Token).ConfigureAwait(false); } /// @@ -187,7 +187,7 @@ public async Task JoinRoom(Room room, string? password = null) Debug.Assert(room.RoomID != null); var cancellationSource = joinCancellationSource = new CancellationTokenSource(); - await initRoom(room, r => JoinRoom(room.RoomID.Value, password ?? room.Password), cancellationSource.Token).ConfigureAwait(false); + await initRoom(room, r => JoinRoomInternal(room.RoomID.Value, password ?? room.Password), cancellationSource.Token).ConfigureAwait(false); } private async Task initRoom(Room room, Func> initFunc, CancellationToken cancellationToken) @@ -236,21 +236,6 @@ protected virtual void OnRoomJoined() { } - /// - /// Creates the with the given settings. - /// - /// The room. - /// The joined - protected abstract Task CreateRoom(MultiplayerRoom room); - - /// - /// Joins the with a given ID. - /// - /// The room ID. - /// An optional password to use when joining the room. - /// The joined . - protected abstract Task JoinRoom(long roomId, string? password = null); - public Task LeaveRoom() { if (Room == null) @@ -279,6 +264,24 @@ public Task LeaveRoom() }); } + /// + /// Creates the with the given settings. + /// + /// The room. + /// The joined + protected abstract Task CreateRoomInternal(MultiplayerRoom room); + + /// + /// Joins the with a given ID. + /// + /// The room ID. + /// An optional password to use when joining the room. + /// The joined . + protected abstract Task JoinRoomInternal(long roomId, string? password = null); + + /// + /// Leaves the currently-joined . + /// protected abstract Task LeaveRoomInternal(); public abstract Task InvitePlayer(int userId); diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 05f3e444054b..068ba277890e 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -75,7 +75,32 @@ private void load(IAPIProvider api) } } - protected override async Task JoinRoom(long roomId, string? password = null) + protected override async Task CreateRoomInternal(MultiplayerRoom room) + { + if (!IsConnected.Value) + throw new OperationCanceledException(); + + Debug.Assert(connection != null); + + try + { + return await connection.InvokeAsync(nameof(IMultiplayerServer.CreateRoom), room).ConfigureAwait(false); + } + catch (HubException exception) + { + if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE) + { + Debug.Assert(connector != null); + + await connector.Reconnect().ConfigureAwait(false); + return await CreateRoomInternal(room).ConfigureAwait(false); + } + + throw; + } + } + + protected override async Task JoinRoomInternal(long roomId, string? password = null) { if (!IsConnected.Value) throw new OperationCanceledException(); @@ -93,7 +118,7 @@ protected override async Task JoinRoom(long roomId, string? pas Debug.Assert(connector != null); await connector.Reconnect().ConfigureAwait(false); - return await JoinRoom(roomId, password).ConfigureAwait(false); + return await JoinRoomInternal(roomId, password).ConfigureAwait(false); } throw; @@ -266,31 +291,6 @@ public override Task RemovePlaylistItem(long playlistItemId) return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); } - protected override async Task CreateRoom(MultiplayerRoom room) - { - if (!IsConnected.Value) - throw new OperationCanceledException(); - - Debug.Assert(connection != null); - - try - { - return await connection.InvokeAsync(nameof(IMultiplayerServer.CreateRoom), room).ConfigureAwait(false); - } - catch (HubException exception) - { - if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE) - { - Debug.Assert(connector != null); - - await connector.Reconnect().ConfigureAwait(false); - return await CreateRoom(room).ConfigureAwait(false); - } - - throw; - } - } - public override Task DisconnectInternal() { if (connector == null) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index d514fc0d7e7c..359b223ad2a2 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -208,7 +208,7 @@ public void ChangeUserBeatmapAvailability(int userId, BeatmapAvailability newBea ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(clone(userId), clone(user.BeatmapAvailability)); } - protected override async Task JoinRoom(long roomId, string? password = null) + protected override async Task JoinRoomInternal(long roomId, string? password = null) { if (RoomJoined || ServerAPIRoom != null) throw new InvalidOperationException("Already joined a room"); @@ -485,7 +485,7 @@ public async Task RemoveUserPlaylistItem(int userId, long playlistItemId) public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(playlistItemId)); - protected override Task CreateRoom(MultiplayerRoom room) + protected override Task CreateRoomInternal(MultiplayerRoom room) { Room apiRoom = new Room(room) { @@ -495,7 +495,7 @@ protected override Task CreateRoom(MultiplayerRoom room) }; AddServerSideRoom(apiRoom, api.LocalUser.Value); - return JoinRoom(apiRoom.RoomID!.Value, room.Settings.Password); + return JoinRoomInternal(apiRoom.RoomID!.Value, room.Settings.Password); } private async Task changeMatchType(MatchType type) From a198b0830affdab861037c0a90525946fa446b5d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 Jan 2025 17:18:01 +0900 Subject: [PATCH 08/13] Add comment indicating RoomManager shouldn't exist --- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 3abb4098fb9f..a1b61ea7a335 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -13,6 +13,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { + // Todo: This class should be inlined into the lounge. public partial class RoomManager : Component, IRoomManager { public event Action? RoomsUpdated; From f2d8ea299777ad6168eb90d04a574d10bf083837 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 Jan 2025 18:25:55 +0900 Subject: [PATCH 09/13] Fix incorrect continuation --- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 279b140d36c0..72b581eac113 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -472,7 +472,7 @@ private void apply() { client.CreateRoom(room).ContinueWith(t => Schedule(() => { - if (t.IsCompleted) + if (t.IsCompletedSuccessfully) onSuccess(room); else if (t.IsFaulted) { From 6dbf466009f6ab12f2613eebb970a2a1d1e101b3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 Jan 2025 18:30:11 +0900 Subject: [PATCH 10/13] Fix incorrect exception handling In particular, when the exception is: `AggregateException { AggregateException { HubException } }`, then the existing code will only unwrap the first aggregate exception. The overlay's code was copied from the extension so both have been adjusted here. --- .../Online/Multiplayer/MultiplayerClientExtensions.cs | 9 +++------ .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 8 ++------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs index d846e7f5669c..1cc5a8e70a80 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; +using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Logging; namespace osu.Game.Online.Multiplayer @@ -16,12 +17,8 @@ public static void FireAndForget(this Task task, Action? onSuccess = null, Actio { if (t.IsFaulted) { - Exception? exception = t.Exception; - - if (exception is AggregateException ae) - exception = ae.InnerException; - - Debug.Assert(exception != null); + Debug.Assert(t.Exception != null); + Exception exception = t.Exception.AsSingular(); if (exception.GetHubExceptionMessage() is string message) // Hub exceptions generally contain something we can show the user directly. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 72b581eac113..2a5a83fadfa5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -476,12 +476,8 @@ private void apply() onSuccess(room); else if (t.IsFaulted) { - Exception? exception = t.Exception; - - if (exception is AggregateException ae) - exception = ae.InnerException; - - Debug.Assert(exception != null); + Debug.Assert(t.Exception != null); + Exception exception = t.Exception.AsSingular(); if (exception.GetHubExceptionMessage() is string message) onError(message); From e9d6411e615ba85a2989511a9f374682b20d25cf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 23 Jan 2025 19:10:11 +0900 Subject: [PATCH 11/13] Clean up error handling --- .../Match/MultiplayerMatchSettingsOverlay.cs | 58 +++++++++---------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 2a5a83fadfa5..eda3bace40a7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -463,9 +463,9 @@ private void apply() .ContinueWith(t => Schedule(() => { if (t.IsCompletedSuccessfully) - onSuccess(room); + onSuccess(); else - onError(t.Exception?.AsSingular().Message ?? "Error changing settings."); + onError(t.Exception, "Error changing settings"); })); } else @@ -473,26 +473,16 @@ private void apply() client.CreateRoom(room).ContinueWith(t => Schedule(() => { if (t.IsCompletedSuccessfully) - onSuccess(room); - else if (t.IsFaulted) - { - Debug.Assert(t.Exception != null); - Exception exception = t.Exception.AsSingular(); - - if (exception.GetHubExceptionMessage() is string message) - onError(message); - else - onError($"Error creating room: {exception}"); - } + onSuccess(); else - onError("Error creating room."); + onError(t.Exception, "Error creating room"); })); } } private void hideError() => ErrorText.FadeOut(50); - private void onSuccess(Room room) => Schedule(() => + private void onSuccess() => Schedule(() => { Debug.Assert(applyingSettingsOperation != null); @@ -502,28 +492,34 @@ private void onSuccess(Room room) => Schedule(() => applyingSettingsOperation = null; }); - private void onError(string text) => Schedule(() => + private void onError(Exception? exception, string description) { - Debug.Assert(applyingSettingsOperation != null); + if (exception is AggregateException aggregateException) + exception = aggregateException.AsSingular(); - // see https://github.com/ppy/osu-web/blob/2c97aaeb64fb4ed97c747d8383a35b30f57428c7/app/Models/Multiplayer/PlaylistItem.php#L48. - const string not_found_prefix = "beatmaps not found:"; + string message = exception?.GetHubExceptionMessage() ?? $"{description} ({exception?.Message})"; - if (text.StartsWith(not_found_prefix, StringComparison.Ordinal)) + Schedule(() => { - ErrorText.Text = "The selected beatmap is not available online."; - room.Playlist.SingleOrDefault()?.MarkInvalid(); - } - else - { - ErrorText.Text = text; - } + Debug.Assert(applyingSettingsOperation != null); - ErrorText.FadeIn(50); + // see https://github.com/ppy/osu-web/blob/2c97aaeb64fb4ed97c747d8383a35b30f57428c7/app/Models/Multiplayer/PlaylistItem.php#L48. + const string not_found_prefix = "beatmaps not found:"; - applyingSettingsOperation.Dispose(); - applyingSettingsOperation = null; - }); + if (message.StartsWith(not_found_prefix, StringComparison.Ordinal)) + { + ErrorText.Text = "The selected beatmap is not available online."; + room.Playlist.SingleOrDefault()?.MarkInvalid(); + } + else + ErrorText.Text = message; + + ErrorText.FadeIn(50); + + applyingSettingsOperation.Dispose(); + applyingSettingsOperation = null; + }); + } protected override void Dispose(bool isDisposing) { From ab4162e2aafc4e246ba070870e4967ab7a6e00cb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 25 Jan 2025 19:27:21 +0900 Subject: [PATCH 12/13] Various refactorings and cleanups --- .../TestSceneMultiplayerLoungeSubScreen.cs | 28 +++++-------------- .../TestScenePlaylistsLoungeSubScreen.cs | 28 +++---------------- .../Multiplayer/IMultiplayerLoungeServer.cs | 5 ++++ .../Online/Multiplayer/MultiplayerClient.cs | 3 +- osu.Game/Online/Rooms/CreateRoomRequest.cs | 2 ++ osu.Game/Online/Rooms/JoinRoomRequest.cs | 2 ++ .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- .../OnlinePlay/Lounge/IOnlinePlayLounge.cs | 6 ++-- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 6 ++-- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 3 ++ .../Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- .../Playlists/PlaylistsLoungeSubScreen.cs | 2 +- 12 files changed, 34 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs index 4a259149e254..eb649acd2d2a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs @@ -40,7 +40,7 @@ public override void SetUpSteps() [Test] public void TestJoinRoomWithoutPassword() { - addRoom(false); + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: false)); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("join room", () => InputManager.Key(Key.Enter)); @@ -50,7 +50,7 @@ public void TestJoinRoomWithoutPassword() [Test] public void TestPopoverHidesOnBackButton() { - addRoom(true); + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); @@ -70,7 +70,7 @@ public void TestPopoverHidesOnBackButton() [Test] public void TestPopoverHidesOnLeavingScreen() { - addRoom(true); + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); @@ -86,7 +86,7 @@ public void TestJoinRoomWithIncorrectPasswordViaButton() { DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; - addRoom(true); + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); @@ -105,7 +105,7 @@ public void TestJoinRoomWithIncorrectPasswordViaEnter() { DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; - addRoom(true); + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); @@ -124,7 +124,7 @@ public void TestJoinRoomWithCorrectPassword() { DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; - addRoom(true); + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); @@ -139,7 +139,7 @@ public void TestJoinRoomWithPasswordViaKeyboardOnly() { DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; - addRoom(true); + AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true)); AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("attempt join room", () => InputManager.Key(Key.Enter)); AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); @@ -149,20 +149,6 @@ public void TestJoinRoomWithPasswordViaKeyboardOnly() AddAssert("room joined", () => MultiplayerClient.RoomJoined); } - private void addRoom(bool withPassword) - { - int initialRoomCount = 0; - - AddStep("add room", () => - { - initialRoomCount = roomsContainer.Rooms.Count; - RoomManager.AddRooms(1, withPassword: withPassword); - loungeScreen.RefreshRooms(); - }); - - AddUntilStep("wait for room to appear", () => roomsContainer.Rooms.Count == initialRoomCount + 1); - } - protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new MultiplayerTestSceneDependencies(); } } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 0897a3b2f568..53c7873de52a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -35,12 +35,7 @@ public override void SetUpSteps() [Test] public void TestManyRooms() { - AddStep("add rooms", () => - { - RoomManager.AddRooms(500); - loungeScreen.RefreshRooms(); - }); - + AddStep("add rooms", () => RoomManager.AddRooms(500)); AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 500); } @@ -49,12 +44,7 @@ public void TestScrollByDraggingRooms() { AddStep("reset mouse", () => InputManager.ReleaseButton(MouseButton.Left)); - AddStep("add rooms", () => - { - RoomManager.AddRooms(30); - loungeScreen.RefreshRooms(); - }); - + AddStep("add rooms", () => RoomManager.AddRooms(30)); AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30); AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0])); @@ -71,12 +61,7 @@ public void TestScrollByDraggingRooms() [Test] public void TestScrollSelectedIntoView() { - AddStep("add rooms", () => - { - RoomManager.AddRooms(30); - loungeScreen.RefreshRooms(); - }); - + AddStep("add rooms", () => RoomManager.AddRooms(30)); AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30); AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0])); @@ -90,12 +75,7 @@ public void TestScrollSelectedIntoView() [Test] public void TestEnteringRoomTakesLeaseOnSelection() { - AddStep("add rooms", () => - { - RoomManager.AddRooms(1); - loungeScreen.RefreshRooms(); - }); - + AddStep("add rooms", () => RoomManager.AddRooms(1)); AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 1); AddAssert("selected room is not disabled", () => !loungeScreen.SelectedRoom.Disabled); diff --git a/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs index c5eb6f9b36c6..0ee9fa54cd81 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerLoungeServer.cs @@ -10,6 +10,11 @@ namespace osu.Game.Online.Multiplayer /// public interface IMultiplayerLoungeServer { + /// + /// Request to create a multiplayer room. + /// + /// The room to create. + /// The created multiplayer room. Task CreateRoom(MultiplayerRoom room); /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index a8f314d37292..6749ed953574 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -168,7 +168,7 @@ private void load() public async Task CreateRoom(Room room) { if (Room != null) - throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); + throw new InvalidOperationException("Cannot create a multiplayer room while already in one."); var cancellationSource = joinCancellationSource = new CancellationTokenSource(); await initRoom(room, r => CreateRoomInternal(new MultiplayerRoom(room)), cancellationSource.Token).ConfigureAwait(false); @@ -212,6 +212,7 @@ await runOnUpdateThreadAsync(() => APIRoom.RoomID = joinedRoom.RoomID; APIRoom.Playlist = joinedRoom.Playlist.Select(item => new PlaylistItem(item)).ToArray(); APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); + // The server will null out the end date upon the host joining the room, but the null value is never communicated to the client. APIRoom.EndDate = null; Debug.Assert(LocalUser != null); diff --git a/osu.Game/Online/Rooms/CreateRoomRequest.cs b/osu.Game/Online/Rooms/CreateRoomRequest.cs index 9773bb5e7d97..5b2ea77aad66 100644 --- a/osu.Game/Online/Rooms/CreateRoomRequest.cs +++ b/osu.Game/Online/Rooms/CreateRoomRequest.cs @@ -15,6 +15,8 @@ public class CreateRoomRequest : APIRequest public CreateRoomRequest(Room room) { Room = room; + + // Also copy back to the source model, since it is likely to have been stored elsewhere. Success += r => Room.CopyFrom(r); } diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs index 13e7ac8c84a6..610e88724214 100644 --- a/osu.Game/Online/Rooms/JoinRoomRequest.cs +++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs @@ -16,6 +16,8 @@ public JoinRoomRequest(Room room, string? password) { Room = room; Password = password; + + // Also copy back to the source model, since it is likely to have been stored elsewhere. Success += r => Room.CopyFrom(r); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 032a231ad3f7..5de35ef10145 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -162,7 +162,7 @@ public MenuItem[] ContextMenuItems { new OsuMenuItem("Create copy", MenuItemType.Standard, () => { - lounge?.Clone(Room); + lounge?.OpenCopy(Room); }) }; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/IOnlinePlayLounge.cs b/osu.Game/Screens/OnlinePlay/Lounge/IOnlinePlayLounge.cs index 8fa7d0751f0f..73ab84af13b3 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/IOnlinePlayLounge.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/IOnlinePlayLounge.cs @@ -18,10 +18,10 @@ public interface IOnlinePlayLounge void Join(Room room, string? password, Action? onSuccess = null, Action? onFailure = null); /// - /// Clones the given room and opens it as a fresh (not-yet-created) one. + /// Copies the given room and opens it as a fresh (not-yet-created) one. /// - /// The room to clone. - void Clone(Room room); + /// The room to copy. + void OpenCopy(Room room); /// /// Closes the given room. diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index df17063fdf79..0e08e398a494 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -309,7 +309,7 @@ public void Join(Room room, string? password, Action? onSuccess = null, Ac joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); - TryJoin(room, password, r => + JoinInternal(room, password, r => { Open(room); joiningRoomOperation?.Dispose(); @@ -323,9 +323,9 @@ public void Join(Room room, string? password, Action? onSuccess = null, Ac }); }); - protected abstract void TryJoin(Room room, string? password, Action onSuccess, Action onFailure); + protected abstract void JoinInternal(Room room, string? password, Action onSuccess, Action onFailure); - public void Clone(Room room) + public void OpenCopy(Room room) { Debug.Assert(room.RoomID != null); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index d37f3b877c26..80b3961f4413 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -353,6 +353,9 @@ public override bool OnExiting(ScreenExitEvent e) return base.OnExiting(e); } + /// + /// Parts from the current room. + /// protected abstract void PartRoom(); private bool ensureExitConfirmed() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index e901ecbdce51..873a9cde8896 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -81,7 +81,7 @@ protected override FilterCriteria CreateFilterCriteria() protected override ListingPollingComponent CreatePollingComponent() => new MultiplayerListingPollingComponent(); - protected override void TryJoin(Room room, string? password, Action onSuccess, Action onFailure) + protected override void JoinInternal(Room room, string? password, Action onSuccess, Action onFailure) { client.JoinRoom(room, password).ContinueWith(result => { diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs index 92415e0eb180..6ed367328c1d 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs @@ -60,7 +60,7 @@ protected override FilterCriteria CreateFilterCriteria() return criteria; } - protected override void TryJoin(Room room, string? password, Action onSuccess, Action onFailure) + protected override void JoinInternal(Room room, string? password, Action onSuccess, Action onFailure) { var joinRoomRequest = new JoinRoomRequest(room, password); From 8c85616d1c8677a859bf007291997b092786f94c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 11 Feb 2025 21:28:21 +0900 Subject: [PATCH 13/13] Fix test --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorList.cs index 66c465cbedfa..bd1e15d06d29 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorList.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorList.cs @@ -32,7 +32,7 @@ public void TestBasics() Bindable playingState = new Bindable(); GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), healthProcessor: new OsuHealthProcessor(0), localUserPlayingState: playingState); TestSpectatorClient spectatorClient = new TestSpectatorClient(); - TestMultiplayerClient multiplayerClient = new TestMultiplayerClient(new TestMultiplayerRoomManager(new TestRoomRequestsHandler())); + TestMultiplayerClient multiplayerClient = new TestMultiplayerClient(new TestRoomRequestsHandler()); AddStep("create spectator list", () => {