From 772471a6d826a730ca17176f2c542b7e4d2ba44a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 09:34:39 +0300 Subject: [PATCH 1/9] Add failing test case --- .../Gameplay/TestScenePauseWhenInactive.cs | 63 ++++++++++++++++--- osu.Game/Screens/Play/Player.cs | 10 +-- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index e43e5ba3ce..15412fea00 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -1,28 +1,28 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state. public class TestScenePauseWhenInactive : OsuPlayerTestScene { - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) - { - var beatmap = (Beatmap)base.CreateBeatmap(ruleset); - - beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000); - - return beatmap; - } - [Resolved] private GameHost host { get; set; } @@ -33,10 +33,53 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("resume player", () => Player.GameplayClockContainer.Start()); AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value); + + AddStep("progress time to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime)); + AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); + } + + /// + /// Tests that if a pause from focus lose is performed while in pause cooldown, + /// the player will still pause after the cooldown is finished. + /// + [Test] + public void TestPauseWhileInCooldown() + { + AddStep("resume player", () => Player.GameplayClockContainer.Start()); + AddStep("skip to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime)); + + AddStep("set inactive", () => ((Bindable)host.IsActive).Value = false); + AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); + + AddStep("set active", () => ((Bindable)host.IsActive).Value = true); + + AddStep("resume player", () => Player.Resume()); + AddStep("click resume overlay", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("pause cooldown active", () => Player.PauseCooldownActive); + AddStep("set inactive again", () => ((Bindable)host.IsActive).Value = false); AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); - AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime); } protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + return new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 30000 }, + new HitCircle { StartTime = 35000 }, + }, + }; + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => new TestWorkingBeatmap(beatmap, storyboard, Audio); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 74059da21a..a7acda926b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -667,6 +667,9 @@ namespace osu.Game.Screens.Play private double? lastPauseActionTime; + public bool PauseCooldownActive => + lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; + private bool canPause => // must pass basic screen conditions (beatmap loaded, instance allows pause) LoadedBeatmapSuccessfully && Configuration.AllowPause && ValidForResume @@ -675,10 +678,7 @@ namespace osu.Game.Screens.Play // cannot pause if we are already in a fail state && !HasFailed // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state. - && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive)); - - private bool pauseCooldownActive => - lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; + && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !PauseCooldownActive)); private bool canResume => // cannot resume from a non-paused state @@ -812,7 +812,7 @@ namespace osu.Game.Screens.Play // ValidForResume is false when restarting if (ValidForResume) { - if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) + if (PauseCooldownActive && !GameplayClockContainer.IsPaused.Value) // still want to block if we are within the cooldown period and not already paused. return true; } From 4436585aa4dea5bec025e5bf816ed23008b4c87e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 09:35:29 +0300 Subject: [PATCH 2/9] Keep attempting to pause gameplay while window not active --- osu.Game/Screens/Play/Player.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a7acda926b..72d9a60c91 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -427,11 +427,16 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() { - if (!PauseOnFocusLost || breakTracker.IsBreakTime.Value) + if (!PauseOnFocusLost || DrawableRuleset.HasReplayLoaded.Value || breakTracker.IsBreakTime.Value) return; if (gameActive.Value == false) - Pause(); + { + if (canPause) + Pause(); + else + Scheduler.AddDelayed(updatePauseOnFocusLostState, 200); + } } private IBeatmap loadPlayableBeatmap() From ddd1dcff88428460cfcde74c963196a0518924fe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:33:26 +0300 Subject: [PATCH 3/9] Attempt pausing every single frame --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 72d9a60c91..fa545859d4 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -435,7 +435,7 @@ namespace osu.Game.Screens.Play if (canPause) Pause(); else - Scheduler.AddDelayed(updatePauseOnFocusLostState, 200); + Scheduler.AddOnce(updatePauseOnFocusLostState); } } From 0771154dd2831f4d7de9d28913965f49df8909ce Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:42:30 +0300 Subject: [PATCH 4/9] Make `PauseCooldownActive` protected and expose on test class --- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Tests/Visual/TestPlayer.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fa545859d4..8c816e8030 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -672,7 +672,7 @@ namespace osu.Game.Screens.Play private double? lastPauseActionTime; - public bool PauseCooldownActive => + protected bool PauseCooldownActive => lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; private bool canPause => diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index f47391ce6a..0addc9de75 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual public new HealthProcessor HealthProcessor => base.HealthProcessor; + public new bool PauseCooldownActive => base.PauseCooldownActive; + public readonly List Results = new List(); public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) From fe5e45ea8180931f1c4c8d02a162e50b0000f186 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:43:33 +0300 Subject: [PATCH 5/9] Move gameplay cursor outside instead and fix potential failure --- .../Gameplay/TestScenePauseWhenInactive.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index 15412fea00..fa596c4823 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -2,21 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Platform; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; -using osuTK.Input; +using osuTK; namespace osu.Game.Tests.Visual.Gameplay { @@ -45,6 +42,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPauseWhileInCooldown() { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + AddStep("resume player", () => Player.GameplayClockContainer.Start()); AddStep("skip to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime)); @@ -54,14 +53,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set active", () => ((Bindable)host.IsActive).Value = true); AddStep("resume player", () => Player.Resume()); - AddStep("click resume overlay", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - AddAssert("pause cooldown active", () => Player.PauseCooldownActive); - AddStep("set inactive again", () => ((Bindable)host.IsActive).Value = false); + bool pauseCooldownActive = false; + + AddStep("set inactive again", () => + { + pauseCooldownActive = Player.PauseCooldownActive; + ((Bindable)host.IsActive).Value = false; + }); + AddAssert("pause cooldown active", () => pauseCooldownActive); AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); } From f6c279ab00d6af3231e09332e4d35b4c2ce7e106 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:45:45 +0300 Subject: [PATCH 6/9] Add assert ensuring player resumed properly --- osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index fa596c4823..49c1163c6c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -53,6 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set active", () => ((Bindable)host.IsActive).Value = true); AddStep("resume player", () => Player.Resume()); + AddAssert("unpaused", () => !Player.GameplayClockContainer.IsPaused.Value); bool pauseCooldownActive = false; From 5e9040c29108cf423ddcf0d6ea418f4aa6690b46 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 22 Feb 2021 16:26:35 +0300 Subject: [PATCH 7/9] Use "pausing supported" conditional instead --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5a86ac646a..0046eea91c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -427,7 +427,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() { - if (!PauseOnFocusLost || DrawableRuleset.HasReplayLoaded.Value || breakTracker.IsBreakTime.Value) + if (!PauseOnFocusLost || pausingSupportedByCurrentState || breakTracker.IsBreakTime.Value) return; if (gameActive.Value == false) From 5493c55da7287bea7f77651ea0474587cc426626 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 22 Feb 2021 16:59:35 +0300 Subject: [PATCH 8/9] Fix silly mistake --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0046eea91c..2ded1752da 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -427,7 +427,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() { - if (!PauseOnFocusLost || pausingSupportedByCurrentState || breakTracker.IsBreakTime.Value) + if (!PauseOnFocusLost || !pausingSupportedByCurrentState || breakTracker.IsBreakTime.Value) return; if (gameActive.Value == false) From 6a5c6febc56567cd5acb43fdb274f9918c8ef230 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 13:23:32 +0900 Subject: [PATCH 9/9] Add inline comment explaining the retry loop --- osu.Game/Screens/Play/Player.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2ded1752da..e81efdac78 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -434,6 +434,8 @@ namespace osu.Game.Screens.Play { bool paused = Pause(); + // if the initial pause could not be satisfied, the pause cooldown may be active. + // reschedule the pause attempt until it can be achieved. if (!paused) Scheduler.AddOnce(updatePauseOnFocusLostState); }