diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index b1615cc7f6..73ac850d3f 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; @@ -34,13 +34,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. /// @@ -65,6 +66,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 => { @@ -75,19 +82,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) @@ -95,7 +103,6 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer if (!string.IsNullOrEmpty(message)) Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important); - startedEvent.Set(); Schedule(() => PerformExit(false)); } @@ -117,7 +124,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); @@ -127,9 +138,9 @@ 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(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 4bd6340751..4be7592e12 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -731,9 +731,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); @@ -748,6 +745,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)