diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 54b2e66f2f..b3401c916b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -3,10 +3,10 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -40,8 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TestReplayRecorder recorder; - [Cached] - private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); + private GameplayState gameplayState; [SetUpSteps] public void SetUpSteps() @@ -52,81 +51,15 @@ namespace osu.Game.Tests.Visual.Gameplay { replay = new Replay(); - Add(new GridContainer + gameplayState = TestGameplayState.Create(new OsuRuleset()); + gameplayState.Score.Replay = replay; + + Child = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - Recorder = recorder = new TestReplayRecorder(new Score - { - Replay = replay, - ScoreInfo = - { - BeatmapInfo = gameplayState.Beatmap.BeatmapInfo, - Ruleset = new OsuRuleset().RulesetInfo, - } - }) - { - ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Brown, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Recording", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - }, - new Drawable[] - { - playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - ReplayInputHandler = new TestFramedReplayInputHandler(replay) - { - GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.DarkBlue, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Playback", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - } - } - }); + CachedDependencies = new (Type, object)[] { (typeof(GameplayState), gameplayState) }, + Child = createContent(), + }; }); } @@ -203,6 +136,74 @@ namespace osu.Game.Tests.Visual.Gameplay recorder = null; } + private Drawable createContent() => new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Recorder = recorder = new TestReplayRecorder(gameplayState.Score) + { + ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Recording", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + }, + new Drawable[] + { + playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + ReplayInputHandler = new TestFramedReplayInputHandler(replay) + { + GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkBlue, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Playback", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + } + } + }; + public class TestFramedReplayInputHandler : FramedReplayInputHandler { public TestFramedReplayInputHandler(Replay replay) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs index c55e98c1a8..9ad8ac086c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; @@ -43,6 +44,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); } + [Test] + public void TestRestart() + { + AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing); + + AddStep("exit player", () => Player.Exit()); + AddStep("reload player", LoadPlayer); + AddUntilStep("wait for player load", () => Player.IsLoaded && Player.Alpha == 1); + + AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing); + + AddWaitStep("wait", 5); + AddUntilStep("spectator client still sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing); + } + public override void TearDownSteps() { base.TearDownSteps(); diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 68c8b57019..b5e1c8a45f 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -206,6 +206,11 @@ namespace osu.Game.Online.Spectator if (!IsPlaying) return; + // Disposal can take some time, leading to EndPlaying potentially being called after a future play session. + // Account for this by ensuring the score of the current play matches the one in the provided state. + if (currentScore != state.Score) + return; + if (pendingFrames.Count > 0) purgePendingFrames(); diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 9fb62106f3..c2162d4df2 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -70,6 +70,7 @@ namespace osu.Game.Screens.Play { ScoreInfo = { + BeatmapInfo = beatmap.BeatmapInfo, Ruleset = ruleset.RulesetInfo } };