From e86e9bfae625351e5323c9393e011190dd58c7b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Dec 2020 15:32:55 +0900 Subject: [PATCH 1/2] Don't begin gameplay until all users are in a completely prepared state --- .../RealtimeMultiplayer/RealtimePlayer.cs | 39 ++++++++++++------- osu.Game/Screens/Play/Player.cs | 18 +++++++-- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index 085c52cb85..20b184bed3 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -3,12 +3,12 @@ using System; using System.Diagnostics; -using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.RealtimeMultiplayer; using osu.Game.Scoring; @@ -33,13 +33,14 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer private IBindable isConnected; private readonly TaskCompletionSource resultsReady = new TaskCompletionSource(); - private readonly ManualResetEventSlim startedEvent = new ManualResetEventSlim(); [CanBeNull] private MultiplayerGameplayLeaderboard leaderboard; private readonly int[] userIds; + private LoadingLayer loadingDisplay; + /// /// Construct a multiplayer player. /// @@ -60,6 +61,12 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer client.MatchStarted += onMatchStarted; client.ResultsReady += onResultsReady; + ScoreProcessor.HasCompleted.BindValueChanged(completed => + { + // wait for server to tell us that results are ready (see SubmitScore implementation) + loadingDisplay.Show(); + }); + isConnected = client.IsConnected.GetBoundCopy(); isConnected.BindValueChanged(connected => { @@ -70,19 +77,20 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer } }, true); - 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."); - return; - } - Debug.Assert(client.Room != null); // 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); + + HUDOverlay.Add(loadingDisplay = new LoadingLayer(DrawableRuleset) { Depth = float.MaxValue }); + } + + protected override void StartGameplay() + { + // block base call, but let the server know we are ready to start. + loadingDisplay.Show(); + + client.ChangeState(MultiplayerUserState.Loaded).ContinueWith(task => failAndBail(task.Exception?.Message ?? "Server error"), TaskContinuationOptions.NotOnRanToCompletion); } private void failAndBail(string message = null) @@ -90,7 +98,6 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer if (!string.IsNullOrEmpty(message)) Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important); - startedEvent.Set(); Schedule(() => PerformExit(false)); } @@ -112,7 +119,11 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer padding + HUDOverlay.TopScoringElementsHeight); } - private void onMatchStarted() => startedEvent.Set(); + private void onMatchStarted() => Scheduler.Add(() => + { + loadingDisplay.Hide(); + base.StartGameplay(); + }); private void onResultsReady() => resultsReady.SetResult(true); @@ -124,7 +135,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer // Await up to 30 seconds for results to become available (3 api request timeouts). // This is arbitrary just to not leave the player in an essentially deadlocked state if any connection issues occur. - await Task.WhenAny(resultsReady.Task, Task.Delay(TimeSpan.FromSeconds(30))); + await Task.WhenAny(resultsReady.Task, Task.Delay(TimeSpan.FromSeconds(60))); } protected override ResultsScreen CreateResults(ScoreInfo score) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2bc84ce5d5..3f761d9e11 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -723,9 +723,6 @@ namespace osu.Game.Screens.Play storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; - GameplayClockContainer.Restart(); - GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); - foreach (var mod in Mods.Value.OfType()) mod.ApplyToPlayer(this); @@ -740,6 +737,21 @@ namespace osu.Game.Screens.Play mod.ApplyToTrack(musicController.CurrentTrack); updateGameplayState(); + + GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); + StartGameplay(); + } + + /// + /// Called to trigger the starting of the gameplay clock and underlying gameplay. + /// This will be called on entering the player screen once. A derived class may block the first call to this to delay the start of gameplay. + /// + protected virtual void StartGameplay() + { + if (GameplayClockContainer.GameplayClock.IsRunning) + throw new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running"); + + GameplayClockContainer.Restart(); } public override void OnSuspending(IScreen next) From 261c250b46dae0db37151601a2d789c0e789b5f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 24 Dec 2020 11:33:49 +0100 Subject: [PATCH 2/2] Update outdated comment --- osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index 20b184bed3..b1179ea7cd 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer await client.ChangeState(MultiplayerUserState.FinishedPlay); - // Await up to 30 seconds for results to become available (3 api request timeouts). + // Await up to 60 seconds for results to become available (6 api request timeouts). // This is arbitrary just to not leave the player in an essentially deadlocked state if any connection issues occur. await Task.WhenAny(resultsReady.Task, Task.Delay(TimeSpan.FromSeconds(60))); }