Merge pull request #11833 from frenzibyte/fix-pofl-on-cooldown

Keep pausing gameplay on focus loss if cannot on first attempt
This commit is contained in:
Dean Herbert 2021-02-24 00:15:58 +09:00 committed by GitHub
commit e679a1916f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 19 deletions

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -8,21 +9,17 @@ using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
[HeadlessTest] // we alter unsafe properties on the game host to test inactive window state. [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state.
public class TestScenePauseWhenInactive : OsuPlayerTestScene 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] [Resolved]
private GameHost host { get; set; } private GameHost host { get; set; }
@ -33,10 +30,57 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("resume player", () => Player.GameplayClockContainer.Start()); AddStep("resume player", () => Player.GameplayClockContainer.Start());
AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value); 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);
}
/// <summary>
/// Tests that if a pause from focus lose is performed while in pause cooldown,
/// the player will still pause after the cooldown is finished.
/// </summary>
[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));
AddStep("set inactive", () => ((Bindable<bool>)host.IsActive).Value = false);
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
AddStep("set active", () => ((Bindable<bool>)host.IsActive).Value = true);
AddStep("resume player", () => Player.Resume());
AddAssert("unpaused", () => !Player.GameplayClockContainer.IsPaused.Value);
bool pauseCooldownActive = false;
AddStep("set inactive again", () =>
{
pauseCooldownActive = Player.PauseCooldownActive;
((Bindable<bool>)host.IsActive).Value = false;
});
AddAssert("pause cooldown active", () => pauseCooldownActive);
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); 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 TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true);
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
return new Beatmap
{
HitObjects = new List<HitObject>
{
new HitCircle { StartTime = 30000 },
new HitCircle { StartTime = 35000 },
},
};
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
=> new TestWorkingBeatmap(beatmap, storyboard, Audio);
} }
} }

View File

@ -427,11 +427,18 @@ namespace osu.Game.Screens.Play
private void updatePauseOnFocusLostState() private void updatePauseOnFocusLostState()
{ {
if (!PauseOnFocusLost || breakTracker.IsBreakTime.Value) if (!PauseOnFocusLost || !pausingSupportedByCurrentState || breakTracker.IsBreakTime.Value)
return; return;
if (gameActive.Value == false) if (gameActive.Value == false)
Pause(); {
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);
}
} }
private IBeatmap loadPlayableBeatmap() private IBeatmap loadPlayableBeatmap()
@ -674,6 +681,9 @@ namespace osu.Game.Screens.Play
private double? lastPauseActionTime; private double? lastPauseActionTime;
protected bool PauseCooldownActive =>
lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown;
/// <summary> /// <summary>
/// A set of conditionals which defines whether the current game state and configuration allows for /// A set of conditionals which defines whether the current game state and configuration allows for
/// pausing to be attempted via <see cref="Pause"/>. If false, the game should generally exit if a user pause /// pausing to be attempted via <see cref="Pause"/>. If false, the game should generally exit if a user pause
@ -684,11 +694,9 @@ namespace osu.Game.Screens.Play
LoadedBeatmapSuccessfully && Configuration.AllowPause && ValidForResume LoadedBeatmapSuccessfully && Configuration.AllowPause && ValidForResume
// replays cannot be paused and exit immediately // replays cannot be paused and exit immediately
&& !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.HasReplayLoaded.Value
// cannot pause if we are already in a fail state
&& !HasFailed; && !HasFailed;
private bool pauseCooldownActive =>
lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown;
private bool canResume => private bool canResume =>
// cannot resume from a non-paused state // cannot resume from a non-paused state
GameplayClockContainer.IsPaused.Value GameplayClockContainer.IsPaused.Value
@ -697,12 +705,12 @@ namespace osu.Game.Screens.Play
// already resuming // already resuming
&& !IsResuming; && !IsResuming;
public void Pause() public bool Pause()
{ {
if (!pausingSupportedByCurrentState) return; if (!pausingSupportedByCurrentState) return false;
if (!IsResuming && pauseCooldownActive) if (!IsResuming && PauseCooldownActive)
return; return false;
if (IsResuming) if (IsResuming)
{ {
@ -713,6 +721,7 @@ namespace osu.Game.Screens.Play
GameplayClockContainer.Stop(); GameplayClockContainer.Stop();
PauseOverlay.Show(); PauseOverlay.Show();
lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime;
return true;
} }
public void Resume() public void Resume()

View File

@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual
public new HealthProcessor HealthProcessor => base.HealthProcessor; public new HealthProcessor HealthProcessor => base.HealthProcessor;
public new bool PauseCooldownActive => base.PauseCooldownActive;
public readonly List<JudgementResult> Results = new List<JudgementResult>(); public readonly List<JudgementResult> Results = new List<JudgementResult>();
public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false)