From 00d50150de6e39cbdff44e7f5a98c41a3c57b6a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 15:49:22 +0900 Subject: [PATCH 1/7] Ensure the current room is left at a mutliplayer client level on client disconnection --- .../RealtimeMultiplayer/RealtimeMultiplayerClient.cs | 4 ++++ .../RealtimeMultiplayer/StatefulMultiplayerClient.cs | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/osu.Game/Online/RealtimeMultiplayer/RealtimeMultiplayerClient.cs b/osu.Game/Online/RealtimeMultiplayer/RealtimeMultiplayerClient.cs index 75bb578a29..5cbf3be8ca 100644 --- a/osu.Game/Online/RealtimeMultiplayer/RealtimeMultiplayerClient.cs +++ b/osu.Game/Online/RealtimeMultiplayer/RealtimeMultiplayerClient.cs @@ -130,7 +130,11 @@ namespace osu.Game.Online.RealtimeMultiplayer public override async Task LeaveRoom() { if (!isConnected.Value) + { + // even if not connected, make sure the local room state can be cleaned up. + await base.LeaveRoom(); return; + } if (Room == null) return; diff --git a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs index 4ebd648689..9680387fcc 100644 --- a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs @@ -75,6 +75,16 @@ namespace osu.Game.Online.RealtimeMultiplayer // Todo: This is temporary, until the multiplayer server returns the item id on match start or otherwise. private int playlistItemId; + protected StatefulMultiplayerClient() + { + IsConnected.BindValueChanged(connected => + { + // clean up local room state on server disconnect. + if (!connected.NewValue) + LeaveRoom(); + }); + } + /// /// Joins the for a given API . /// From 12df3056e6d27d2da5d29a172708e849162eaa3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 15:58:50 +0900 Subject: [PATCH 2/7] Ensure appropriate screens handle exiting when the server gets disconnected I would have liked for this to be handled via the `OnRoomChanged` event flow, but this isn't present in RealtimeMatchSubScreen due to inheritence woes. --- .../RealtimeMultiplayer/RealtimeMatchSubScreen.cs | 14 ++++++++++++++ .../Multi/RealtimeMultiplayer/RealtimePlayer.cs | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs index cdab1435c0..8405fc196b 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs @@ -4,8 +4,10 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.Multiplayer; using osu.Game.Online.RealtimeMultiplayer; @@ -34,6 +36,8 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer private RealtimeMatchSettingsOverlay settingsOverlay; + private IBindable isConnected; + public RealtimeMatchSubScreen(Room room) { Title = room.RoomID.Value == null ? "New match" : room.Name.Value; @@ -173,6 +177,16 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer Playlist.BindCollectionChanged(onPlaylistChanged, true); client.LoadRequested += onLoadRequested; + + isConnected = client.IsConnected.GetBoundCopy(); + isConnected.BindValueChanged(connected => + { + if (!connected.NewValue) + { + Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); + Schedule(this.Exit); + } + }, true); } public override bool OnBackButton() diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index c6d44686b5..d74ccdd32f 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.Multiplayer; @@ -30,6 +31,8 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer private readonly TaskCompletionSource resultsReady = new TaskCompletionSource(); private readonly ManualResetEventSlim startedEvent = new ManualResetEventSlim(); + private IBindable isConnected; + public RealtimePlayer(PlaylistItem playlistItem) : base(playlistItem, false) { @@ -43,6 +46,15 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer client.MatchStarted += onMatchStarted; client.ResultsReady += onResultsReady; + + isConnected = client.IsConnected.GetBoundCopy(); + isConnected.BindValueChanged(connected => + { + if (!connected.NewValue) + // messaging to the user about this disconnect will be provided by the RealtimeMatchSubScreen. + Schedule(this.Exit); + }, true); + client.ChangeState(MultiplayerUserState.Loaded); if (!startedEvent.Wait(TimeSpan.FromSeconds(30))) From a1d42dc4a061a8fb2f50064c485cbd3cf07186be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 16:17:55 +0900 Subject: [PATCH 3/7] Don't allow creating or joining a room when not connected to server --- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 2 +- .../RealtimeLoungeSubScreen.cs | 17 +++++++++++++++++ .../RealtimeMultiplayer/RealtimeRoomManager.cs | 6 ++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index 44c893363b..6b08745dd7 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Multi.Lounge /// /// Push a room as a new subscreen. /// - public void Open(Room room) + public virtual void Open(Room room) { // Handles the case where a room is clicked 3 times in quick succession if (!this.IsCurrentScreen()) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeLoungeSubScreen.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeLoungeSubScreen.cs index 9fbf0c4654..b53ec94519 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeLoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeLoungeSubScreen.cs @@ -1,7 +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 osu.Framework.Allocation; +using osu.Framework.Logging; using osu.Game.Online.Multiplayer; +using osu.Game.Online.RealtimeMultiplayer; using osu.Game.Screens.Multi.Lounge; using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Match; @@ -13,5 +16,19 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer protected override FilterControl CreateFilterControl() => new RealtimeFilterControl(); protected override RoomSubScreen CreateRoomSubScreen(Room room) => new RealtimeMatchSubScreen(room); + + [Resolved] + private StatefulMultiplayerClient client { get; set; } + + public override void Open(Room room) + { + if (!client.IsConnected.Value) + { + Logger.Log("Not currently connected to the multiplayer server.", LoggingTarget.Runtime, LogLevel.Important); + return; + } + + base.Open(room); + } } } diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs index f982574eb3..2f60f504de 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs @@ -43,6 +43,12 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer public override void JoinRoom(Room room, 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.Status.Value is RoomStatusEnded) From 569c4092efe42a55caabc5a08c7f7bc227f3b1e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 16:19:03 +0900 Subject: [PATCH 4/7] Move notification to stateful client so it is only shown to the user from one location --- .../RealtimeMultiplayer/StatefulMultiplayerClient.cs | 4 ++++ .../Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs index 9680387fcc..79d82a8d02 100644 --- a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; @@ -81,7 +82,10 @@ namespace osu.Game.Online.RealtimeMultiplayer { // clean up local room state on server disconnect. if (!connected.NewValue) + { + Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); LeaveRoom(); + } }); } diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs index 8405fc196b..807ea74404 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.Multiplayer; using osu.Game.Online.RealtimeMultiplayer; @@ -18,6 +17,7 @@ using osu.Game.Screens.Multi.RealtimeMultiplayer.Match; using osu.Game.Screens.Multi.RealtimeMultiplayer.Participants; using osu.Game.Screens.Play; using osu.Game.Users; +using ParticipantsList = osu.Game.Screens.Multi.RealtimeMultiplayer.Participants.ParticipantsList; namespace osu.Game.Screens.Multi.RealtimeMultiplayer { @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer new Drawable[] { new ParticipantsListHeader() }, new Drawable[] { - new Participants.ParticipantsList + new ParticipantsList { RelativeSizeAxes = Axes.Both }, @@ -182,10 +182,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer isConnected.BindValueChanged(connected => { if (!connected.NewValue) - { - Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); Schedule(this.Exit); - } }, true); } From f5d27b40a8f5c9dd8b3e849360425ed540b79c39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 16:32:58 +0900 Subject: [PATCH 5/7] Standardise flow for aborting realtime player exit to avoid double-exit call --- .../Multi/RealtimeMultiplayer/RealtimePlayer.cs | 12 ++++++------ osu.Game/Screens/Play/Player.cs | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index d74ccdd32f..edec40890e 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -51,8 +51,12 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer isConnected.BindValueChanged(connected => { if (!connected.NewValue) + { + startedEvent.Set(); + // messaging to the user about this disconnect will be provided by the RealtimeMatchSubScreen. - Schedule(this.Exit); + Schedule(PerformImmediateExit); + } }, true); client.ChangeState(MultiplayerUserState.Loaded); @@ -61,11 +65,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer { Logger.Log("Failed to start the multiplayer match in time.", LoggingTarget.Runtime, LogLevel.Important); - Schedule(() => - { - ValidForResume = false; - this.Exit(); - }); + Schedule(PerformImmediateExit); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c539dff5d9..c6265c48d2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -386,7 +386,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; fadeOut(true); - performImmediateExit(); + PerformImmediateExit(); }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, @@ -458,7 +458,7 @@ namespace osu.Game.Screens.Play return playable; } - private void performImmediateExit() + protected void PerformImmediateExit() { // if a restart has been requested, cancel any pending completion (user has shown intent to restart). completionProgressDelegate?.Cancel(); @@ -498,7 +498,7 @@ namespace osu.Game.Screens.Play RestartRequested?.Invoke(); if (this.IsCurrentScreen()) - performImmediateExit(); + PerformImmediateExit(); else this.MakeCurrent(); } From 91021eb8c45b0826bf0503cc9d5d4fac822fe005 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 16:49:17 +0900 Subject: [PATCH 6/7] Remove unused using --- osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index edec40890e..9e2ba9b04a 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; -using osu.Framework.Screens; using osu.Game.Online.Multiplayer; using osu.Game.Online.RealtimeMultiplayer; using osu.Game.Scoring; From 980e85ce25f087f28306c530e15b3d7916b2b5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 15:51:26 +0100 Subject: [PATCH 7/7] Refactor player exit logic to convey intention better --- .../RealtimeMultiplayer/RealtimePlayer.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index 0d8e636450..e467e5fcf8 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer startedEvent.Set(); // messaging to the user about this disconnect will be provided by the RealtimeMatchSubScreen. - Schedule(PerformImmediateExit); + Schedule(() => PerformExit(false)); } }, true); @@ -71,7 +71,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer { Logger.Log("Failed to start the multiplayer match in time.", LoggingTarget.Runtime, LogLevel.Important); - Schedule(PerformImmediateExit); + Schedule(() => PerformExit(false)); } Debug.Assert(client.Room != null); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c6265c48d2..2bc84ce5d5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -386,7 +386,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; fadeOut(true); - PerformImmediateExit(); + PerformExit(true); }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, @@ -458,20 +458,30 @@ namespace osu.Game.Screens.Play return playable; } - protected void PerformImmediateExit() + /// + /// Exits the . + /// + /// + /// Whether the exit is requested by the user, or a higher-level game component. + /// Pausing is allowed only in the former case. + /// + protected void PerformExit(bool userRequested) { // if a restart has been requested, cancel any pending completion (user has shown intent to restart). completionProgressDelegate?.Cancel(); ValidForResume = false; - performUserRequestedExit(); + if (!this.IsCurrentScreen()) return; + + if (userRequested) + performUserRequestedExit(); + else + this.Exit(); } private void performUserRequestedExit() { - if (!this.IsCurrentScreen()) return; - if (ValidForResume && HasFailed && !FailOverlay.IsPresent) { failAnimation.FinishTransforms(true); @@ -498,7 +508,7 @@ namespace osu.Game.Screens.Play RestartRequested?.Invoke(); if (this.IsCurrentScreen()) - PerformImmediateExit(); + PerformExit(true); else this.MakeCurrent(); }