From 6517acc510885301d7bc0c3cf82d7638f7fc63cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Dec 2020 19:09:59 +0900 Subject: [PATCH 01/27] Add leaderboard display to realtime player --- .../RealtimeMultiplayer/RealtimePlayer.cs | 23 +++++++++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 7 +++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index c6d44686b5..453e7e6140 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -12,7 +13,9 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.RealtimeMultiplayer; using osu.Game.Scoring; using osu.Game.Screens.Multi.Play; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; +using osuTK; namespace osu.Game.Screens.Multi.RealtimeMultiplayer { @@ -30,6 +33,8 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer private readonly TaskCompletionSource resultsReady = new TaskCompletionSource(); private readonly ManualResetEventSlim startedEvent = new ManualResetEventSlim(); + private MultiplayerGameplayLeaderboard leaderboard; + public RealtimePlayer(PlaylistItem playlistItem) : base(playlistItem, false) { @@ -55,6 +60,24 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer this.Exit(); }); } + + Debug.Assert(client.Room != null); + + int[] userIds = client.Room.Users.Where(u => u.State >= MultiplayerUserState.WaitingForLoad).Select(u => u.UserID).ToArray(); + + // todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area. + LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, userIds), HUDOverlay.Add); + } + + protected override void Update() + { + base.Update(); + + const float padding = 44; // enough margin to avoid the hit error display. + + leaderboard.Position = new Vector2( + padding, + padding + HUDOverlay.TopScoringElementsHeight); } private void onMatchStarted() => startedEvent.Set(); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 50195d571c..3dffab8102 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -28,6 +28,11 @@ namespace osu.Game.Screens.Play public const Easing FADE_EASING = Easing.Out; + /// + /// The total height of all the top of screen scoring elements. + /// + public float TopScoringElementsHeight { get; private set; } + public readonly KeyCounterDisplay KeyCounter; public readonly SkinnableComboCounter ComboCounter; public readonly SkinnableScoreCounter ScoreCounter; @@ -209,7 +214,7 @@ namespace osu.Game.Screens.Play // HACK: for now align with the accuracy counter. // this is done for the sake of hacky legacy skins which extend the health bar to take up the full screen area. // it only works with the default skin due to padding offsetting it *just enough* to coexist. - topRightElements.Y = ToLocalSpace(AccuracyCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y; + topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(AccuracyCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y; bottomRightElements.Y = -Progress.Height; } From e3483147e2f682000121bd1534f494efbd42e6c8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 22 Dec 2020 13:53:01 +0300 Subject: [PATCH 02/27] Move track looping logic into subscreens --- osu.Game/Screens/Multi/Match/RoomSubScreen.cs | 52 ++++++++++++++++++ osu.Game/Screens/Multi/Multiplayer.cs | 53 ------------------- 2 files changed, 52 insertions(+), 53 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/RoomSubScreen.cs b/osu.Game/Screens/Multi/Match/RoomSubScreen.cs index 0cc9a4354e..21316a98ce 100644 --- a/osu.Game/Screens/Multi/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/RoomSubScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; +using osu.Game.Overlays; using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.Multi.Match @@ -23,6 +24,9 @@ namespace osu.Game.Screens.Multi.Match [Resolved(typeof(Room), nameof(Room.Playlist))] protected BindableList Playlist { get; private set; } + [Resolved] + private MusicController music { get; set; } + [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -61,6 +65,30 @@ namespace osu.Game.Screens.Multi.Match var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); + if (this.IsCurrentScreen()) + applyTrackLooping(); + } + + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + + music?.EnsurePlayingSomething(); + applyTrackLooping(); + } + + public override void OnSuspending(IScreen next) + { + cancelTrackLooping(); + base.OnSuspending(next); + } + + public override void OnResuming(IScreen last) + { + base.OnResuming(last); + + music?.EnsurePlayingSomething(); + applyTrackLooping(); } public override bool OnExiting(IScreen next) @@ -68,7 +96,31 @@ namespace osu.Game.Screens.Multi.Match RoomManager?.PartRoom(); Mods.Value = Array.Empty(); + cancelTrackLooping(); + return base.OnExiting(next); } + + private void applyTrackLooping() + { + var track = Beatmap.Value?.Track; + + if (track != null) + { + track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + track.Looping = true; + } + } + + private void cancelTrackLooping() + { + var track = Beatmap?.Value?.Track; + + if (track != null) + { + track.Looping = false; + track.RestartPoint = 0; + } + } } } diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index eae779421d..de2e0d58c9 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -198,8 +197,6 @@ namespace osu.Game.Screens.Multi { this.FadeIn(); waves.Show(); - - beginHandlingTrack(); } public override void OnResuming(IScreen last) @@ -209,8 +206,6 @@ namespace osu.Game.Screens.Multi base.OnResuming(last); - beginHandlingTrack(); - UpdatePollingRate(isIdle.Value); } @@ -219,8 +214,6 @@ namespace osu.Game.Screens.Multi this.ScaleTo(1.1f, 250, Easing.InSine); this.FadeOut(250); - endHandlingTrack(); - UpdatePollingRate(isIdle.Value); } @@ -235,8 +228,6 @@ namespace osu.Game.Screens.Multi if (screenStack.CurrentScreen != null) loungeSubScreen.MakeCurrent(); - endHandlingTrack(); - base.OnExiting(next); return false; } @@ -275,17 +266,6 @@ namespace osu.Game.Screens.Multi /// The created . protected virtual Room CreateNewRoom() => new Room { Name = { Value = $"{api.LocalUser}'s awesome room" } }; - private void beginHandlingTrack() - { - Beatmap.BindValueChanged(updateTrack, true); - } - - private void endHandlingTrack() - { - cancelLooping(); - Beatmap.ValueChanged -= updateTrack; - } - private void screenPushed(IScreen lastScreen, IScreen newScreen) { subScreenChanged(lastScreen, newScreen); @@ -322,43 +302,10 @@ namespace osu.Game.Screens.Multi UpdatePollingRate(isIdle.Value); createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200); - - updateTrack(); } protected IScreen CurrentSubScreen => screenStack.CurrentScreen; - private void updateTrack(ValueChangedEvent _ = null) - { - if (screenStack.CurrentScreen is RoomSubScreen) - { - var track = Beatmap.Value?.Track; - - if (track != null) - { - track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - track.Looping = true; - - music?.EnsurePlayingSomething(); - } - } - else - { - cancelLooping(); - } - } - - private void cancelLooping() - { - var track = Beatmap?.Value?.Track; - - if (track != null) - { - track.Looping = false; - track.RestartPoint = 0; - } - } - protected abstract RoomManager CreateRoomManager(); protected abstract LoungeSubScreen CreateLounge(); From 32728047044a426d2c076cc143d836412d53b7a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Dec 2020 22:31:40 +0100 Subject: [PATCH 03/27] Fix potential crash when no submission token Can happen because `TimeshiftPlayer` will schedule a screen exit on token retrieval failure, and `RealtimePlayer`'s BDL won't even attempt to create a leaderboard in that case. --- .../Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index 453e7e6140..7824b414f2 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.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Screens; @@ -33,6 +34,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer private readonly TaskCompletionSource resultsReady = new TaskCompletionSource(); private readonly ManualResetEventSlim startedEvent = new ManualResetEventSlim(); + [CanBeNull] private MultiplayerGameplayLeaderboard leaderboard; public RealtimePlayer(PlaylistItem playlistItem) @@ -72,6 +74,13 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer protected override void Update() { base.Update(); + adjustLeaderboardPosition(); + } + + private void adjustLeaderboardPosition() + { + if (leaderboard == null) + return; const float padding = 44; // enough margin to avoid the hit error display. From be427a4ec0fd7f30189f3b37941844483ad95d62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 14:20:35 +0900 Subject: [PATCH 04/27] Fix realtime leaderboard showing accuracy based on final base score, not rolling --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 23 ++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 10d0cc2865..4b2e2bf715 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Given a minimal set of inputs, return the computed score and accuracy for the tracked beatmap / mods combination. + /// Given a minimal set of inputs, return the computed score and accuracy for the tracked beatmap / mods combination, at the current point in time. /// /// The to compute the total score in. /// The maximum combo achievable in the beatmap. @@ -252,15 +252,28 @@ namespace osu.Game.Rulesets.Scoring computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; } - double accuracy = calculateAccuracyRatio(computedBaseScore); + double pointInTimeAccuracy = calculateAccuracyRatio(computedBaseScore, true); double comboRatio = calculateComboRatio(maxCombo); - double score = GetScore(mode, maxAchievableCombo, accuracy, comboRatio, scoreResultCounts); + double score = GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), comboRatio, scoreResultCounts); - return (score, accuracy); + return (score, pointInTimeAccuracy); + } + + /// + /// Get the accuracy fraction for the provided base score. + /// + /// The score to be used for accuracy calculation. + /// Whether the rolling base score should be used (ie. for the current point in time based on Apply/Reverted results). + /// The computed accuracy. + private double calculateAccuracyRatio(double baseScore, bool preferRolling = false) + { + if (preferRolling && rollingMaxBaseScore != 0) + return baseScore / rollingMaxBaseScore; + + return maxBaseScore > 0 ? baseScore / maxBaseScore : 0; } - private double calculateAccuracyRatio(double baseScore) => maxBaseScore > 0 ? baseScore / maxBaseScore : 0; private double calculateComboRatio(int maxCombo) => maxAchievableCombo > 0 ? (double)maxCombo / maxAchievableCombo : 1; private double getBonusScore(Dictionary statistics) From 286884421d74d8cd565533bdd723448b88416dc3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 23 Dec 2020 08:47:34 +0300 Subject: [PATCH 05/27] Apply track looping and play on track change --- osu.Game/Screens/Multi/Match/RoomSubScreen.cs | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/RoomSubScreen.cs b/osu.Game/Screens/Multi/Match/RoomSubScreen.cs index 21316a98ce..4f5d2a5b3e 100644 --- a/osu.Game/Screens/Multi/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/RoomSubScreen.cs @@ -41,6 +41,37 @@ namespace osu.Game.Screens.Multi.Match managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); + + if (music != null) + music.TrackChanged += applyToTrack; + } + + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + applyToTrack(); + } + + public override void OnSuspending(IScreen next) + { + resetTrack(); + base.OnSuspending(next); + } + + public override void OnResuming(IScreen last) + { + base.OnResuming(last); + applyToTrack(); + } + + public override bool OnExiting(IScreen next) + { + RoomManager?.PartRoom(); + Mods.Value = Array.Empty(); + + resetTrack(); + + return base.OnExiting(next); } private void selectedItemChanged() @@ -65,54 +96,25 @@ namespace osu.Game.Screens.Multi.Match var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); - if (this.IsCurrentScreen()) - applyTrackLooping(); } - public override void OnEntering(IScreen last) + private void applyToTrack(WorkingBeatmap _ = default, TrackChangeDirection __ = default) { - base.OnEntering(last); + if (!this.IsCurrentScreen()) + return; - music?.EnsurePlayingSomething(); - applyTrackLooping(); - } - - public override void OnSuspending(IScreen next) - { - cancelTrackLooping(); - base.OnSuspending(next); - } - - public override void OnResuming(IScreen last) - { - base.OnResuming(last); - - music?.EnsurePlayingSomething(); - applyTrackLooping(); - } - - public override bool OnExiting(IScreen next) - { - RoomManager?.PartRoom(); - Mods.Value = Array.Empty(); - - cancelTrackLooping(); - - return base.OnExiting(next); - } - - private void applyTrackLooping() - { var track = Beatmap.Value?.Track; if (track != null) { track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; track.Looping = true; + + music?.EnsurePlayingSomething(); } } - private void cancelTrackLooping() + private void resetTrack() { var track = Beatmap?.Value?.Track; From 00d50150de6e39cbdff44e7f5a98c41a3c57b6a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 15:49:22 +0900 Subject: [PATCH 06/27] 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 07/27] 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 08/27] 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 09/27] 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 10/27] 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 11/27] 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 d27b83d678bb4c9f0525b685880a5e9dd136ee90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 16:51:11 +0900 Subject: [PATCH 12/27] More correctly handle fire-and-forget async call --- .../RealtimeMultiplayer/RealtimePlayer.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index 9e2ba9b04a..0f8c0f247c 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -51,21 +51,25 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer { if (!connected.NewValue) { - startedEvent.Set(); - // messaging to the user about this disconnect will be provided by the RealtimeMatchSubScreen. - Schedule(PerformImmediateExit); + failAndBail(); } }, true); - client.ChangeState(MultiplayerUserState.Loaded); + client.ChangeState(MultiplayerUserState.Loaded).ContinueWith(task => + failAndBail(task.Exception?.Message ?? "Server error"), TaskContinuationOptions.NotOnRanToCompletion); if (!startedEvent.Wait(TimeSpan.FromSeconds(30))) - { - Logger.Log("Failed to start the multiplayer match in time.", LoggingTarget.Runtime, LogLevel.Important); + failAndBail("Failed to start the multiplayer match in time."); + } - Schedule(PerformImmediateExit); - } + private void failAndBail(string message = null) + { + if (!string.IsNullOrEmpty(message)) + Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important); + + startedEvent.Set(); + Schedule(PerformImmediateExit); } private void onMatchStarted() => startedEvent.Set(); From c3c3364d399b915f9d0d3044dc3cd045830bd3dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 16:56:51 +0900 Subject: [PATCH 13/27] Simplify error handling of JoinRoom call --- .../RealtimeRoomManager.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs index 2f60f504de..8bdf2bdc1a 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs @@ -83,15 +83,19 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer { Debug.Assert(room.RoomID.Value != null); - var joinTask = multiplayerClient.JoinRoom(room); - joinTask.ContinueWith(_ => Schedule(() => onSuccess?.Invoke(room)), TaskContinuationOptions.OnlyOnRanToCompletion); - joinTask.ContinueWith(t => + multiplayerClient.JoinRoom(room).ContinueWith(t => { - PartRoom(); - if (t.Exception != null) - Logger.Error(t.Exception, "Failed to join multiplayer room."); - Schedule(() => onError?.Invoke(t.Exception?.ToString() ?? string.Empty)); - }, TaskContinuationOptions.NotOnRanToCompletion); + if (t.IsCompletedSuccessfully) + Schedule(() => onSuccess?.Invoke(room)); + else + { + if (t.Exception != null) + Logger.Error(t.Exception, "Failed to join multiplayer room."); + + PartRoom(); + Schedule(() => onError?.Invoke(t.Exception?.ToString() ?? string.Empty)); + } + }); } private void updatePolling() From 1864da00e69806eefb389fd0a5742c0477eb419d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 17:10:02 +0900 Subject: [PATCH 14/27] Add extension method to handle cases of fire-and-forget async usage --- osu.Game/Extensions/TaskExtensions.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 osu.Game/Extensions/TaskExtensions.cs diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs new file mode 100644 index 0000000000..913a622d9b --- /dev/null +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; +using osu.Framework.Logging; + +namespace osu.Game.Extensions +{ + public static class TaskExtensions + { + /// + /// Denote a task which is to be run without local error handling logic, where failure is not catastrophic. + /// Avoids unobserved exceptions from being fired. + /// + /// The task. + /// Whether errors should be logged as important, or silently ignored. + public static void FireAndForget(this Task task, bool logOnError = false) + { + task.ContinueWith(t => + { + if (logOnError) + Logger.Log($"Error running task: {t.Exception?.Message ?? "unknown"}", LoggingTarget.Runtime, LogLevel.Important); + }, TaskContinuationOptions.NotOnRanToCompletion); + } + } +} From 7cc38f03d10a44c30c5edb59e28f2bdc46e377dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 17:10:34 +0900 Subject: [PATCH 15/27] Use extension method in all call sites of fire-and-forget async usage --- .../RealtimeMultiplayer/StatefulMultiplayerClient.cs | 5 +++-- .../Multi/RealtimeMultiplayer/Match/RealtimeReadyButton.cs | 7 ++++--- .../RealtimeMultiplayer/Participants/ParticipantPanel.cs | 3 ++- .../Multi/RealtimeMultiplayer/RealtimeMatchSongSelect.cs | 2 +- .../Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs | 3 ++- .../Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs | 4 ++-- .../Multi/RealtimeMultiplayer/RealtimeRoomManager.cs | 4 +++- 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs index 79d82a8d02..3196f10f6f 100644 --- a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. + // See the LICENCE file in the repository root for full licence text. #nullable enable @@ -14,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -84,7 +85,7 @@ namespace osu.Game.Online.RealtimeMultiplayer if (!connected.NewValue) { Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); - LeaveRoom(); + LeaveRoom().FireAndForget(); } }); } diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeReadyButton.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeReadyButton.cs index 09487e9831..59f9d5e1ec 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeReadyButton.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeReadyButton.cs @@ -7,6 +7,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Online.API; @@ -105,13 +106,13 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer.Match return; if (localUser.State == MultiplayerUserState.Idle) - Client.ChangeState(MultiplayerUserState.Ready); + Client.ChangeState(MultiplayerUserState.Ready).FireAndForget(true); else { if (Room?.Host?.Equals(localUser) == true) - Client.StartMatch(); + Client.StartMatch().FireAndForget(true); else - Client.ChangeState(MultiplayerUserState.Idle); + Client.ChangeState(MultiplayerUserState.Idle).FireAndForget(true); } } diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/Participants/ParticipantPanel.cs index a4ff2ce346..fd16754045 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/Participants/ParticipantPanel.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -176,7 +177,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer.Participants if (Room.Host?.UserID != api.LocalUser.Value.Id) return; - Client.TransferHost(targetUser); + Client.TransferHost(targetUser).FireAndForget(true); }) }; } diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSongSelect.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSongSelect.cs index f3dab93089..4a8e398008 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSongSelect.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSongSelect.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer client.ChangeSettings(item: item).ContinueWith(t => { - return Schedule(() => + Schedule(() => { loadingLayer.Hide(); diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs index 6455701d31..d5b891f2cc 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Extensions; using osu.Game.Online.Multiplayer; using osu.Game.Online.RealtimeMultiplayer; using osu.Game.Screens.Multi.Components; @@ -21,7 +22,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer base.OnResuming(last); if (client.Room != null) - client.ChangeState(MultiplayerUserState.Idle); + client.ChangeState(MultiplayerUserState.Idle).FireAndForget(true); } protected override void UpdatePollingRate(bool isIdle) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index 0f8c0f247c..7c6b33cddd 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -56,8 +56,8 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer } }, true); - client.ChangeState(MultiplayerUserState.Loaded).ContinueWith(task => - failAndBail(task.Exception?.Message ?? "Server error"), TaskContinuationOptions.NotOnRanToCompletion); + client.ChangeState(MultiplayerUserState.Loaded) + .ContinueWith(task => failAndBail(task.Exception?.Message ?? "Server error"), TaskContinuationOptions.NotOnRanToCompletion); if (!startedEvent.Wait(TimeSpan.FromSeconds(30))) failAndBail("Failed to start the multiplayer match in time."); diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs index 8bdf2bdc1a..98a0e5b694 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Game.Extensions; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.RoomStatuses; using osu.Game.Online.RealtimeMultiplayer; @@ -68,7 +69,8 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer var joinedRoom = JoinedRoom.Value; base.PartRoom(); - multiplayerClient.LeaveRoom(); + + multiplayerClient.LeaveRoom().FireAndForget(); // Todo: This is not the way to do this. Basically when we're the only participant and the room closes, there's no way to know if this is actually the case. // This is delayed one frame because upon exiting the match subscreen, multiplayer updates the polling rate and messes with polling. From 0ddcab574f05416953361e920a1af22856a16828 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 17:14:58 +0900 Subject: [PATCH 16/27] Rename method to avoid weird code analysis rule --- osu.Game/Extensions/TaskExtensions.cs | 2 +- .../Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs | 2 +- .../Multi/RealtimeMultiplayer/Match/RealtimeReadyButton.cs | 6 +++--- .../RealtimeMultiplayer/Participants/ParticipantPanel.cs | 2 +- .../Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs | 2 +- .../Multi/RealtimeMultiplayer/RealtimeRoomManager.cs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs index 913a622d9b..a1215d786b 100644 --- a/osu.Game/Extensions/TaskExtensions.cs +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -14,7 +14,7 @@ namespace osu.Game.Extensions /// /// The task. /// Whether errors should be logged as important, or silently ignored. - public static void FireAndForget(this Task task, bool logOnError = false) + public static void CatchUnobservedExceptions(this Task task, bool logOnError = false) { task.ContinueWith(t => { diff --git a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs index 3196f10f6f..7b375ca475 100644 --- a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs @@ -85,7 +85,7 @@ namespace osu.Game.Online.RealtimeMultiplayer if (!connected.NewValue) { Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); - LeaveRoom().FireAndForget(); + LeaveRoom().CatchUnobservedExceptions(); } }); } diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeReadyButton.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeReadyButton.cs index 59f9d5e1ec..5bead2b271 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeReadyButton.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeReadyButton.cs @@ -106,13 +106,13 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer.Match return; if (localUser.State == MultiplayerUserState.Idle) - Client.ChangeState(MultiplayerUserState.Ready).FireAndForget(true); + Client.ChangeState(MultiplayerUserState.Ready).CatchUnobservedExceptions(true); else { if (Room?.Host?.Equals(localUser) == true) - Client.StartMatch().FireAndForget(true); + Client.StartMatch().CatchUnobservedExceptions(true); else - Client.ChangeState(MultiplayerUserState.Idle).FireAndForget(true); + Client.ChangeState(MultiplayerUserState.Idle).CatchUnobservedExceptions(true); } } diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/Participants/ParticipantPanel.cs index fd16754045..85393d1bae 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/Participants/ParticipantPanel.cs @@ -177,7 +177,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer.Participants if (Room.Host?.UserID != api.LocalUser.Value.Id) return; - Client.TransferHost(targetUser).FireAndForget(true); + Client.TransferHost(targetUser).CatchUnobservedExceptions(true); }) }; } diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs index d5b891f2cc..6685cf52d6 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer base.OnResuming(last); if (client.Room != null) - client.ChangeState(MultiplayerUserState.Idle).FireAndForget(true); + client.ChangeState(MultiplayerUserState.Idle).CatchUnobservedExceptions(true); } protected override void UpdatePollingRate(bool isIdle) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs index 98a0e5b694..cd337bbb55 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer base.PartRoom(); - multiplayerClient.LeaveRoom().FireAndForget(); + multiplayerClient.LeaveRoom().CatchUnobservedExceptions(); // Todo: This is not the way to do this. Basically when we're the only participant and the room closes, there's no way to know if this is actually the case. // This is delayed one frame because upon exiting the match subscreen, multiplayer updates the polling rate and messes with polling. From 94e4928c4b5feb2bb404173e27fadedbb5d5468d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 11:27:15 +0100 Subject: [PATCH 17/27] Bring back accidentally-removed license header --- .../Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs index 7b375ca475..6331d324a6 100644 --- a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs @@ -1,4 +1,4 @@ - +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable enable From 582b0d2a7467f54f4ae125f79fe6c5611fb88f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 13:47:28 +0100 Subject: [PATCH 18/27] Revert logic to be closer to original Note the reversal of the order of operations in `endHandlingTrack()` (done for extra safety, to ensure no more value changed events can be fired at the point of cancelling looping). --- osu.Game/Screens/Multi/Match/RoomSubScreen.cs | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/RoomSubScreen.cs b/osu.Game/Screens/Multi/Match/RoomSubScreen.cs index 4f5d2a5b3e..b9d7408946 100644 --- a/osu.Game/Screens/Multi/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/RoomSubScreen.cs @@ -41,27 +41,24 @@ namespace osu.Game.Screens.Multi.Match managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); - - if (music != null) - music.TrackChanged += applyToTrack; } public override void OnEntering(IScreen last) { base.OnEntering(last); - applyToTrack(); + beginHandlingTrack(); } public override void OnSuspending(IScreen next) { - resetTrack(); + endHandlingTrack(); base.OnSuspending(next); } public override void OnResuming(IScreen last) { base.OnResuming(last); - applyToTrack(); + beginHandlingTrack(); } public override bool OnExiting(IScreen next) @@ -69,7 +66,7 @@ namespace osu.Game.Screens.Multi.Match RoomManager?.PartRoom(); Mods.Value = Array.Empty(); - resetTrack(); + endHandlingTrack(); return base.OnExiting(next); } @@ -98,7 +95,18 @@ namespace osu.Game.Screens.Multi.Match Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } - private void applyToTrack(WorkingBeatmap _ = default, TrackChangeDirection __ = default) + private void beginHandlingTrack() + { + Beatmap.BindValueChanged(applyLoopingToTrack, true); + } + + private void endHandlingTrack() + { + Beatmap.ValueChanged -= applyLoopingToTrack; + cancelTrackLooping(); + } + + private void applyLoopingToTrack(ValueChangedEvent _ = null) { if (!this.IsCurrentScreen()) return; @@ -114,7 +122,7 @@ namespace osu.Game.Screens.Multi.Match } } - private void resetTrack() + private void cancelTrackLooping() { var track = Beatmap?.Value?.Track; From c5692a5d6aa51c51513e8a34c5ac023cb9326847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 14:18:24 +0100 Subject: [PATCH 19/27] Re-enable carousel selection after error --- .../Multi/RealtimeMultiplayer/RealtimeMatchSongSelect.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSongSelect.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSongSelect.cs index 4a8e398008..8f317800e3 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSongSelect.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSongSelect.cs @@ -65,7 +65,10 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer if (t.IsCompletedSuccessfully) this.Exit(); else + { Logger.Log($"Could not use current beatmap ({t.Exception?.Message})", level: LogLevel.Important); + Carousel.AllowSelection = true; + } }); }); } From 4296f61d6cbe0a8d25a94a9ae22274139c8b1b1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Dec 2020 22:39:14 +0900 Subject: [PATCH 20/27] Tidy up event flow of change settings call --- .../RealtimeMultiplayer/StatefulMultiplayerClient.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs index 6331d324a6..dc999ee2be 100644 --- a/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/RealtimeMultiplayer/StatefulMultiplayerClient.cs @@ -369,7 +369,6 @@ namespace osu.Game.Online.RealtimeMultiplayer if (Room == null) return; - // Update a few properties of the room instantaneously. Schedule(() => { if (Room == null) @@ -377,6 +376,7 @@ namespace osu.Game.Online.RealtimeMultiplayer Debug.Assert(apiRoom != null); + // Update a few properties of the room instantaneously. Room.Settings = settings; apiRoom.Name.Value = Room.Settings.Name; @@ -385,12 +385,12 @@ namespace osu.Game.Online.RealtimeMultiplayer apiRoom.Playlist.Clear(); RoomChanged?.Invoke(); + + var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId); + req.Success += res => updatePlaylist(settings, res); + + api.Queue(req); }); - - var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId); - req.Success += res => updatePlaylist(settings, res); - - api.Queue(req); } private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet onlineSet) 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 21/27] 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(); } From 3b0bf1136642c40fd189cbbd1940d3121f286e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 17:01:01 +0100 Subject: [PATCH 22/27] Fix JoinRoom failing to return canceled token As it turns out, `Task.FromCanceled` expects to receive an already cancelled `CancellationToken`, which `CancellationToken.None` is not. --- .../Online/RealtimeMultiplayer/RealtimeMultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/RealtimeMultiplayer/RealtimeMultiplayerClient.cs b/osu.Game/Online/RealtimeMultiplayer/RealtimeMultiplayerClient.cs index 5cbf3be8ca..026b7176d1 100644 --- a/osu.Game/Online/RealtimeMultiplayer/RealtimeMultiplayerClient.cs +++ b/osu.Game/Online/RealtimeMultiplayer/RealtimeMultiplayerClient.cs @@ -122,7 +122,7 @@ namespace osu.Game.Online.RealtimeMultiplayer protected override Task JoinRoom(long roomId) { if (!isConnected.Value) - return Task.FromCanceled(CancellationToken.None); + return Task.FromCanceled(new CancellationToken(true)); return connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoom), roomId); } From e4959489b70d61f76c6f25e0c7125fd24d96becc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 17:08:28 +0100 Subject: [PATCH 23/27] Improve user-facing error messages in room settings --- .../Match/RealtimeMatchSettingsOverlay.cs | 3 ++- .../Multi/RealtimeMultiplayer/RealtimeRoomManager.cs | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeMatchSettingsOverlay.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeMatchSettingsOverlay.cs index 3e495b490f..a93b1b09d1 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeMatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/Match/RealtimeMatchSettingsOverlay.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -299,7 +300,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer.Match if (t.IsCompletedSuccessfully) onSuccess(currentRoom.Value); else - onError(t.Exception?.Message ?? "Error changing settings."); + onError(t.Exception?.AsSingular().Message ?? "Error changing settings."); })); } else diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs index cd337bbb55..eb6e5fad07 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeRoomManager.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Logging; using osu.Game.Extensions; using osu.Game.Online.Multiplayer; @@ -91,11 +92,13 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer Schedule(() => onSuccess?.Invoke(room)); else { + const string message = "Failed to join multiplayer room."; + if (t.Exception != null) - Logger.Error(t.Exception, "Failed to join multiplayer room."); + Logger.Error(t.Exception, message); PartRoom(); - Schedule(() => onError?.Invoke(t.Exception?.ToString() ?? string.Empty)); + Schedule(() => onError?.Invoke(t.Exception?.AsSingular().Message ?? message)); } }); } From 05d9f2376224b42d9ee56f8ee094374a15dc2801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 16:38:18 +0100 Subject: [PATCH 24/27] Move out create room button to separate class --- osu.Game/Screens/Multi/CreateRoomButton.cs | 31 ++++++++++++++++++++++ osu.Game/Screens/Multi/Multiplayer.cs | 23 ---------------- 2 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Screens/Multi/CreateRoomButton.cs diff --git a/osu.Game/Screens/Multi/CreateRoomButton.cs b/osu.Game/Screens/Multi/CreateRoomButton.cs new file mode 100644 index 0000000000..b501de46ef --- /dev/null +++ b/osu.Game/Screens/Multi/CreateRoomButton.cs @@ -0,0 +1,31 @@ +// 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.Game.Screens.Multi.Match.Components; +using osuTK; + +namespace osu.Game.Screens.Multi +{ + public class CreateRoomButton : PurpleTriangleButton + { + public CreateRoomButton() + { + Size = new Vector2(150, Header.HEIGHT - 20); + Margin = new MarginPadding + { + Top = 10, + Right = 10 + OsuScreen.HORIZONTAL_OVERFLOW_PADDING, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + Triangles.TriangleScale = 1.5f; + + Text = "Create room"; + } + } +} diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index de2e0d58c9..a957eb6824 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -21,9 +21,7 @@ using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Lounge; using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Match; -using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; -using osuTK; namespace osu.Game.Screens.Multi { @@ -332,26 +330,5 @@ namespace osu.Game.Screens.Multi protected override double TransformDuration => 200; } } - - public class CreateRoomButton : PurpleTriangleButton - { - public CreateRoomButton() - { - Size = new Vector2(150, Header.HEIGHT - 20); - Margin = new MarginPadding - { - Top = 10, - Right = 10 + HORIZONTAL_OVERFLOW_PADDING, - }; - } - - [BackgroundDependencyLoader] - private void load() - { - Triangles.TriangleScale = 1.5f; - - Text = "Create room"; - } - } } } From c13acb609aed79df56e883586697be71b72e74c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 21:50:29 +0100 Subject: [PATCH 25/27] Move out sizing logic to multiplayer screen --- osu.Game/Screens/Multi/CreateRoomButton.cs | 12 ------------ osu.Game/Screens/Multi/Multiplayer.cs | 7 +++++++ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Multi/CreateRoomButton.cs b/osu.Game/Screens/Multi/CreateRoomButton.cs index b501de46ef..9e53904510 100644 --- a/osu.Game/Screens/Multi/CreateRoomButton.cs +++ b/osu.Game/Screens/Multi/CreateRoomButton.cs @@ -2,24 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Game.Screens.Multi.Match.Components; -using osuTK; namespace osu.Game.Screens.Multi { public class CreateRoomButton : PurpleTriangleButton { - public CreateRoomButton() - { - Size = new Vector2(150, Header.HEIGHT - 20); - Margin = new MarginPadding - { - Top = 10, - Right = 10 + OsuScreen.HORIZONTAL_OVERFLOW_PADDING, - }; - } - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index a957eb6824..c820eae51f 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -22,6 +22,7 @@ using osu.Game.Screens.Multi.Lounge; using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Match; using osu.Game.Users; +using osuTK; namespace osu.Game.Screens.Multi { @@ -131,6 +132,12 @@ namespace osu.Game.Screens.Multi { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Size = new Vector2(150, Header.HEIGHT - 20), + Margin = new MarginPadding + { + Top = 10, + Right = 10 + HORIZONTAL_OVERFLOW_PADDING, + }, Action = () => OpenNewRoom() }, RoomManager = CreateRoomManager() From 414f886b02d21c24ddaddc273ac747dc1d347b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Dec 2020 22:02:37 +0100 Subject: [PATCH 26/27] Split timeshift & multiplayer "create" buttons Multiplayer button gets new, different "Create match" text, and disable logic in case of a dropped connection to the multiplayer server. --- osu.Game/Screens/Multi/Multiplayer.cs | 18 ++++++++------- .../CreateRealtimeMatchButton.cs | 23 +++++++++++++++++++ .../RealtimeMultiplayer.cs | 3 +++ .../CreateTimeshiftRoomButton.cs} | 4 ++-- .../Multi/Timeshift/TimeshiftMultiplayer.cs | 3 +++ 5 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Screens/Multi/RealtimeMultiplayer/CreateRealtimeMatchButton.cs rename osu.Game/Screens/Multi/{CreateRoomButton.cs => Timeshift/CreateTimeshiftRoomButton.cs} (78%) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index c820eae51f..a7d40a89d3 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -128,18 +128,18 @@ namespace osu.Game.Screens.Multi } }, new Header(screenStack), - createButton = new CreateRoomButton + createButton = CreateNewMultiplayerGameButton().With(button => { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Size = new Vector2(150, Header.HEIGHT - 20), - Margin = new MarginPadding + button.Anchor = Anchor.TopRight; + button.Origin = Anchor.TopRight; + button.Size = new Vector2(150, Header.HEIGHT - 20); + button.Margin = new MarginPadding { Top = 10, Right = 10 + HORIZONTAL_OVERFLOW_PADDING, - }, - Action = () => OpenNewRoom() - }, + }; + button.Action = () => OpenNewRoom(); + }), RoomManager = CreateRoomManager() } }; @@ -315,6 +315,8 @@ namespace osu.Game.Screens.Multi protected abstract LoungeSubScreen CreateLounge(); + protected abstract OsuButton CreateNewMultiplayerGameButton(); + private class MultiplayerWaveContainer : WaveContainer { protected override bool StartHidden => true; diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/CreateRealtimeMatchButton.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/CreateRealtimeMatchButton.cs new file mode 100644 index 0000000000..eda907f8cb --- /dev/null +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/CreateRealtimeMatchButton.cs @@ -0,0 +1,23 @@ +// 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.Bindables; +using osu.Game.Online.RealtimeMultiplayer; +using osu.Game.Screens.Multi.Match.Components; + +namespace osu.Game.Screens.Multi.RealtimeMultiplayer +{ + public class CreateRealtimeMatchButton : PurpleTriangleButton + { + [BackgroundDependencyLoader] + private void load(StatefulMultiplayerClient multiplayerClient) + { + Triangles.TriangleScale = 1.5f; + + Text = "Create match"; + + ((IBindable)Enabled).BindTo(multiplayerClient.IsConnected); + } + } +} diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs index 6685cf52d6..6739a51fe8 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMultiplayer.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Extensions; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.RealtimeMultiplayer; using osu.Game.Screens.Multi.Components; @@ -64,5 +65,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer protected override RoomManager CreateRoomManager() => new RealtimeRoomManager(); protected override LoungeSubScreen CreateLounge() => new RealtimeLoungeSubScreen(); + + protected override OsuButton CreateNewMultiplayerGameButton() => new CreateRealtimeMatchButton(); } } diff --git a/osu.Game/Screens/Multi/CreateRoomButton.cs b/osu.Game/Screens/Multi/Timeshift/CreateTimeshiftRoomButton.cs similarity index 78% rename from osu.Game/Screens/Multi/CreateRoomButton.cs rename to osu.Game/Screens/Multi/Timeshift/CreateTimeshiftRoomButton.cs index 9e53904510..bd9d667630 100644 --- a/osu.Game/Screens/Multi/CreateRoomButton.cs +++ b/osu.Game/Screens/Multi/Timeshift/CreateTimeshiftRoomButton.cs @@ -4,9 +4,9 @@ using osu.Framework.Allocation; using osu.Game.Screens.Multi.Match.Components; -namespace osu.Game.Screens.Multi +namespace osu.Game.Screens.Multi.Timeshift { - public class CreateRoomButton : PurpleTriangleButton + public class CreateTimeshiftRoomButton : PurpleTriangleButton { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/Multi/Timeshift/TimeshiftMultiplayer.cs b/osu.Game/Screens/Multi/Timeshift/TimeshiftMultiplayer.cs index 2ea4857799..d525a3800d 100644 --- a/osu.Game/Screens/Multi/Timeshift/TimeshiftMultiplayer.cs +++ b/osu.Game/Screens/Multi/Timeshift/TimeshiftMultiplayer.cs @@ -3,6 +3,7 @@ using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Lounge; using osu.Game.Screens.Multi.Match; @@ -47,5 +48,7 @@ namespace osu.Game.Screens.Multi.Timeshift protected override RoomManager CreateRoomManager() => new TimeshiftRoomManager(); protected override LoungeSubScreen CreateLounge() => new TimeshiftLoungeSubScreen(); + + protected override OsuButton CreateNewMultiplayerGameButton() => new CreateTimeshiftRoomButton(); } } From d6dadd12faa7c277befcdcefd0eecdc25196cf0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Dec 2020 10:38:53 +0900 Subject: [PATCH 27/27] Send multiplayer user IDs via ctor for better thread safety --- .../RealtimeMultiplayer/RealtimeMatchSubScreen.cs | 10 +++++++++- .../Multi/RealtimeMultiplayer/RealtimePlayer.cs | 13 +++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs index cdab1435c0..468908f83d 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeMatchSubScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -188,7 +189,14 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault(); - private void onLoadRequested() => multiplayer?.Push(new PlayerLoader(() => new RealtimePlayer(SelectedItem.Value))); + private void onLoadRequested() + { + Debug.Assert(client.Room != null); + + int[] userIds = client.Room.Users.Where(u => u.State >= MultiplayerUserState.WaitingForLoad).Select(u => u.UserID).ToArray(); + + multiplayer?.Push(new PlayerLoader(() => new RealtimePlayer(SelectedItem.Value, userIds))); + } protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index 7824b414f2..25543d3d6d 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -37,9 +36,17 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer [CanBeNull] private MultiplayerGameplayLeaderboard leaderboard; - public RealtimePlayer(PlaylistItem playlistItem) + private readonly int[] userIds; + + /// + /// Construct a multiplayer player. + /// + /// The playlist item to be played. + /// The users which are participating in this game. + public RealtimePlayer(PlaylistItem playlistItem, int[] userIds) : base(playlistItem, false) { + this.userIds = userIds; } [BackgroundDependencyLoader] @@ -65,8 +72,6 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer Debug.Assert(client.Room != null); - int[] userIds = client.Room.Users.Where(u => u.State >= MultiplayerUserState.WaitingForLoad).Select(u => u.UserID).ToArray(); - // todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area. LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, userIds), HUDOverlay.Add); }