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..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; @@ -75,6 +76,19 @@ 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) + { + Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); + LeaveRoom(); + } + }); + } + /// /// Joins the for a given API . /// 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/RealtimeMatchSubScreen.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs index cdab1435c0..807ea74404 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs @@ -4,6 +4,7 @@ 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.Screens; @@ -16,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 { @@ -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; @@ -102,7 +106,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer new Drawable[] { new ParticipantsListHeader() }, new Drawable[] { - new Participants.ParticipantsList + new ParticipantsList { RelativeSizeAxes = Axes.Both }, @@ -173,6 +177,13 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer Playlist.BindCollectionChanged(onPlaylistChanged, true); client.LoadRequested += onLoadRequested; + + isConnected = client.IsConnected.GetBoundCopy(); + isConnected.BindValueChanged(connected => + { + if (!connected.NewValue) + 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 7824b414f2..e467e5fcf8 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -8,8 +8,8 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; 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; @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer [Resolved] private StatefulMultiplayerClient client { get; set; } + private IBindable isConnected; + private readonly TaskCompletionSource resultsReady = new TaskCompletionSource(); private readonly ManualResetEventSlim startedEvent = new ManualResetEventSlim(); @@ -50,17 +52,26 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer client.MatchStarted += onMatchStarted; client.ResultsReady += onResultsReady; + + isConnected = client.IsConnected.GetBoundCopy(); + isConnected.BindValueChanged(connected => + { + if (!connected.NewValue) + { + startedEvent.Set(); + + // messaging to the user about this disconnect will be provided by the RealtimeMatchSubScreen. + Schedule(() => PerformExit(false)); + } + }, true); + client.ChangeState(MultiplayerUserState.Loaded); if (!startedEvent.Wait(TimeSpan.FromSeconds(30))) { Logger.Log("Failed to start the multiplayer match in time.", LoggingTarget.Runtime, LogLevel.Important); - Schedule(() => - { - ValidForResume = false; - this.Exit(); - }); + Schedule(() => PerformExit(false)); } Debug.Assert(client.Room != null); 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) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c539dff5d9..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; } - private 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(); }