From 4f4f60248faad019bc3c0501e42de854fea5dbdc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 11 Aug 2021 12:16:25 +0300 Subject: [PATCH 1/3] Add failing test case --- .../TestSceneMultiSpectatorScreen.cs | 51 +++++++++++++++++-- .../Spectate/MultiSpectatorScreen.cs | 5 +- .../Multiplayer/Spectate/PlayerArea.cs | 13 ++++- osu.Game/Screens/Play/Player.cs | 7 +++ 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index b84f7760e4..56cb6036c7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -347,19 +347,44 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert($"{PLAYER_1_ID} score quit still set", () => getLeaderboardScore(PLAYER_1_ID).HasQuit.Value); } - private void loadSpectateScreen(bool waitForPlayerLoad = true) + /// + /// Tests spectating with a gameplay start time set to a negative value. + /// Simulating beatmaps with high or negative time storyboard elements. + /// + [Test] + public void TestNegativeGameplayStartTime() { - AddStep("load screen", () => + start(PLAYER_1_ID); + + loadSpectateScreen(false, -500); + + // to ensure negative gameplay start time does not affect spectator, send frames exactly after StartGameplay(). + // (similar to real spectating sessions in which the first frames get sent between StartGameplay() and player load complete) + AddStep("send frames at gameplay start", () => getInstance(PLAYER_1_ID).OnGameplayStarted += () => SpectatorClient.SendFrames(PLAYER_1_ID, 100)); + + AddUntilStep("wait for player load", () => spectatorScreen.AllPlayersLoaded); + + AddWaitStep("wait for progression", 3); + + assertNotCatchingUp(PLAYER_1_ID); + assertRunning(PLAYER_1_ID); + } + + private void loadSpectateScreen(bool waitForPlayerLoad = true, double? gameplayStartTime = null) + { + AddStep(!gameplayStartTime.HasValue ? "load screen" : $"load screen (start = {gameplayStartTime}ms)", () => { Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap); Ruleset.Value = importedBeatmap.Ruleset; - LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUsers.ToArray())); + LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(playingUsers.ToArray(), gameplayStartTime)); }); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); } + private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); + private void start(int[] userIds, int? beatmapId = null) { AddStep("start play", () => @@ -419,6 +444,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertMuted(int userId, bool muted) => AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted); + private void assertRunning(int userId) + => AddAssert($"{userId} clock running", () => getInstance(userId).GameplayClock.IsRunning); + + private void assertNotCatchingUp(int userId) + => AddAssert($"{userId} in sync", () => !getInstance(userId).GameplayClock.IsCatchingUp); + private void waitForCatchup(int userId) => AddUntilStep($"{userId} not catching up", () => !getInstance(userId).GameplayClock.IsCatchingUp); @@ -429,5 +460,19 @@ namespace osu.Game.Tests.Visual.Multiplayer private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType().Single(s => s.User?.Id == userId); private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray(); + + private class TestMultiSpectatorScreen : MultiSpectatorScreen + { + private readonly double? gameplayStartTime; + + public TestMultiSpectatorScreen(MultiplayerRoomUser[] users, double? gameplayStartTime = null) + : base(users) + { + this.gameplayStartTime = gameplayStartTime; + } + + protected override MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) + => new MasterGameplayClockContainer(beatmap, gameplayStartTime ?? 0, gameplayStartTime.HasValue); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 7350408eba..4646f42d63 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; @@ -68,7 +69,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Container leaderboardContainer; Container scoreDisplayContainer; - masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0); + masterClockContainer = CreateMasterGameplayClockContainer(Beatmap.Value); InternalChildren = new[] { @@ -235,5 +236,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return base.OnBackButton(); } + + protected virtual MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) => new MasterGameplayClockContainer(beatmap, 0); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 48f153ecbe..f5a6777a62 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -24,6 +24,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public class PlayerArea : CompositeDrawable { + /// + /// Raised after is called on . + /// + public event Action OnGameplayStarted; + /// /// Whether a is loaded in the area. /// @@ -93,7 +98,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } }; - stack.Push(new MultiSpectatorPlayerLoader(Score, () => new MultiSpectatorPlayer(Score, GameplayClock))); + stack.Push(new MultiSpectatorPlayerLoader(Score, () => + { + var player = new MultiSpectatorPlayer(Score, GameplayClock); + player.OnGameplayStarted += OnGameplayStarted; + return player; + })); + loadingLayer.Hide(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cfca2d0a3d..0312789b12 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -45,6 +45,11 @@ namespace osu.Game.Screens.Play /// public const double RESULTS_DISPLAY_DELAY = 1000.0; + /// + /// Raised after is called. + /// + public event Action OnGameplayStarted; + public override bool AllowBackButton => false; // handled by HoldForMenuButton protected override UserActivity InitialActivity => new UserActivity.InSoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); @@ -958,7 +963,9 @@ namespace osu.Game.Screens.Play updateGameplayState(); GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); + StartGameplay(); + OnGameplayStarted?.Invoke(); } /// From 3ec193d47e9655de747d6de21b042e36ed5f8211 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jan 2022 20:17:57 +0300 Subject: [PATCH 2/3] Fix spectator clock container incorrectly starting catch-up clock --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs | 2 ++ osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index ececa1e497..615bd41f3f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public SpectatorGameplayClockContainer([NotNull] IClock sourceClock) : base(sourceClock) { + // the container should initially be in a stopped state until the catch-up clock is started by the sync manager. + Stop(); } protected override void Update() diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 0c9b827a41..0fd524f976 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Play /// /// Stops gameplay. /// - public virtual void Stop() => IsPaused.Value = true; + public void Stop() => IsPaused.Value = true; /// /// Resets this and the source to an initial state ready for gameplay. From a49a9ed0a05d778b5af4c6328071bb0f8926d7b5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 31 Jan 2022 17:19:04 +0900 Subject: [PATCH 3/3] Fix incorrect invoke --- osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index f5a6777a62..4979bd906b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate stack.Push(new MultiSpectatorPlayerLoader(Score, () => { var player = new MultiSpectatorPlayer(Score, GameplayClock); - player.OnGameplayStarted += OnGameplayStarted; + player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); return player; }));