From 07077b8f4e52e2aa3902d73a4f936fc2086e7e84 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 21 Dec 2020 00:13:05 +0900 Subject: [PATCH] Add realtime player --- .../Screens/Multi/Play/TimeshiftPlayer.cs | 32 ++++----- .../RealtimeMultiplayer/RealtimePlayer.cs | 67 ++++++++++++++++++- .../RealtimeResultsScreen.cs | 17 +++++ 3 files changed, 99 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeResultsScreen.cs diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index f86d8ff267..e8462088f1 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -25,9 +25,11 @@ namespace osu.Game.Screens.Multi.Play public Action Exited; [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } + protected Bindable RoomId { get; private set; } - private readonly PlaylistItem playlistItem; + protected readonly PlaylistItem PlaylistItem; + + protected int? Token { get; private set; } [Resolved] private IAPIProvider api { get; set; } @@ -38,30 +40,28 @@ namespace osu.Game.Screens.Multi.Play public TimeshiftPlayer(PlaylistItem playlistItem, bool allowPause = true) : base(allowPause) { - this.playlistItem = playlistItem; + PlaylistItem = playlistItem; } - private int? token; - [BackgroundDependencyLoader] private void load() { - token = null; + Token = null; bool failed = false; // Sanity checks to ensure that TimeshiftPlayer matches the settings for the current PlaylistItem - if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != playlistItem.Beatmap.Value.OnlineBeatmapID) + if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != PlaylistItem.Beatmap.Value.OnlineBeatmapID) throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap"); - if (ruleset.Value.ID != playlistItem.Ruleset.Value.ID) + if (ruleset.Value.ID != PlaylistItem.Ruleset.Value.ID) throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); - if (!playlistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) + if (!PlaylistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); - var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID, Game.VersionHash); - req.Success += r => token = r.ID; + var req = new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); + req.Success += r => Token = r.ID; req.Failure += e => { failed = true; @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Multi.Play api.Queue(req); - while (!failed && !token.HasValue) + while (!failed && !Token.HasValue) Thread.Sleep(1000); } @@ -93,8 +93,8 @@ namespace osu.Game.Screens.Multi.Play protected override ResultsScreen CreateResults(ScoreInfo score) { - Debug.Assert(roomId.Value != null); - return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem, true); + Debug.Assert(RoomId.Value != null); + return new TimeshiftResultsScreen(score, RoomId.Value.Value, PlaylistItem, true); } protected override Score CreateScore() @@ -108,10 +108,10 @@ namespace osu.Game.Screens.Multi.Play { await base.SubmitScore(score); - Debug.Assert(token != null); + Debug.Assert(Token != null); var tcs = new TaskCompletionSource(); - var request = new SubmitRoomScoreRequest(token.Value, roomId.Value ?? 0, playlistItem.ID, score.ScoreInfo); + var request = new SubmitRoomScoreRequest(Token.Value, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); request.Success += s => { diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs index d629027246..d654496f40 100644 --- a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimePlayer.cs @@ -1,16 +1,81 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Allocation; using osu.Game.Online.Multiplayer; +using osu.Game.Online.RealtimeMultiplayer; +using osu.Game.Scoring; using osu.Game.Screens.Multi.Play; +using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Multi.RealtimeMultiplayer { public class RealtimePlayer : TimeshiftPlayer { + protected override bool PauseOnFocusLost => false; + + // Disallow fails in multiplayer for now. + protected override bool CheckModsAllowFailure() => false; + + [Resolved] + private StatefulMultiplayerClient client { get; set; } + + private readonly TaskCompletionSource resultsReady = new TaskCompletionSource(); + private bool started; + public RealtimePlayer(PlaylistItem playlistItem) - : base(playlistItem) + : base(playlistItem, false) { } + + [BackgroundDependencyLoader] + private void load() + { + if (Token == null) + return; // Todo: Somehow handle token retrieval failure. + + client.MatchStarted += onMatchStarted; + client.ResultsReady += onResultsReady; + client.ChangeState(MultiplayerUserState.Loaded); + + while (!started) + Thread.Sleep(100); + } + + private void onMatchStarted() => started = true; + + private void onResultsReady() => resultsReady.SetResult(true); + + protected override async Task SubmitScore(Score score) + { + await base.SubmitScore(score); + + await client.ChangeState(MultiplayerUserState.FinishedPlay); + + // 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))); + } + + protected override ResultsScreen CreateResults(ScoreInfo score) + { + Debug.Assert(RoomId.Value != null); + return new RealtimeResultsScreen(score, RoomId.Value.Value, PlaylistItem); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (client != null) + { + client.MatchStarted -= onMatchStarted; + client.ResultsReady -= onResultsReady; + } + } } } diff --git a/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeResultsScreen.cs b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeResultsScreen.cs new file mode 100644 index 0000000000..1598c8fbb5 --- /dev/null +++ b/osu.Game/Screens/Multi/RealtimeMultiplayer/RealtimeResultsScreen.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.Multiplayer; +using osu.Game.Scoring; +using osu.Game.Screens.Multi.Ranking; + +namespace osu.Game.Screens.Multi.RealtimeMultiplayer +{ + public class RealtimeResultsScreen : TimeshiftResultsScreen + { + public RealtimeResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem) + : base(score, roomId, playlistItem, false) + { + } + } +}