diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs index 335ef31019..8df8afe147 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = mod, PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 && - Precision.AlmostEquals(Player.GameplayClockContainer.GameplayClock.Rate, mod.SpeedChange.Value) + Precision.AlmostEquals(Player.GameplayClockContainer.Rate, mod.SpeedChange.Value) }); } } diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 5f403f9487..abd734b96c 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Gameplay }); AddStep("start clock", () => gameplayClockContainer.Start()); - AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.GameplayClock.ElapsedFrameTime > 0); + AddUntilStep("elapsed greater than zero", () => gameplayClockContainer.ElapsedFrameTime > 0); } [Test] @@ -60,16 +60,16 @@ namespace osu.Game.Tests.Gameplay }); AddStep("start clock", () => gameplayClockContainer.Start()); - AddUntilStep("current time greater 2000", () => gameplayClockContainer.GameplayClock.CurrentTime > 2000); + AddUntilStep("current time greater 2000", () => gameplayClockContainer.CurrentTime > 2000); double timeAtReset = 0; AddStep("reset clock", () => { - timeAtReset = gameplayClockContainer.GameplayClock.CurrentTime; + timeAtReset = gameplayClockContainer.CurrentTime; gameplayClockContainer.Reset(); }); - AddAssert("current time < time at reset", () => gameplayClockContainer.GameplayClock.CurrentTime < timeAtReset); + AddAssert("current time < time at reset", () => gameplayClockContainer.CurrentTime < timeAtReset); } [Test] diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index f05244ab88..3ccf6c5d33 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -141,7 +141,7 @@ namespace osu.Game.Tests.Gameplay beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) { - Clock = gameplayContainer.GameplayClock + Clock = gameplayContainer }); }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 7f4276f819..0d80d29cab 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay public double FirstHitObjectTime => DrawableRuleset.Objects.First().StartTime; - public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime; + public double GameplayClockTime => GameplayClockContainer.CurrentTime; protected override void UpdateAfterChildren() { @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Gameplay if (!FirstFrameClockTime.HasValue) { - FirstFrameClockTime = GameplayClockContainer.GameplayClock.CurrentTime; + FirstFrameClockTime = GameplayClockContainer.CurrentTime; AddInternal(new OsuSpriteText { Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} " diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index 3e637f1870..789e7e770f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay base.SetUpSteps(); AddUntilStep("gameplay has started", - () => Player.GameplayClockContainer.GameplayClock.CurrentTime > Player.DrawableRuleset.GameplayStartTime); + () => Player.GameplayClockContainer.CurrentTime > Player.DrawableRuleset.GameplayStartTime); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 71cc1f7b23..cad8c62233 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -313,7 +313,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("pause again", () => { Player.Pause(); - return !Player.GameplayClockContainer.GameplayClock.IsRunning; + return !Player.GameplayClockContainer.IsRunning; }); AddAssert("loop is playing", () => getLoop().IsPlaying); @@ -378,7 +378,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown); private void confirmClockRunning(bool isRunning) => - AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning); + AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.IsRunning == isRunning); protected override bool AllowFail => true; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 5c73db15df..b6b3650c83 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osuTK; @@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.Gameplay private double increment; private GameplayClockContainer gameplayClockContainer; - private GameplayClock gameplayClock; + private IFrameBasedClock gameplayClock; private const double skip_time = 6000; @@ -51,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; gameplayClockContainer.Start(); - gameplayClock = gameplayClockContainer.GameplayClock; + gameplayClock = gameplayClockContainer; }); [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 9eb71b9cf7..146cbfb052 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Gameplay Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time)); - Dependencies.CacheAs(gameplayClockContainer.GameplayClock); + Dependencies.CacheAs(gameplayClockContainer); } [SetUpSteps] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index e2b2ad85a3..e2c825df0b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestStoryboardNoSkipOutro() { CreateTest(); - AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed); - AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration); AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible); } @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("set ShowResults = false", () => showResults = false); }); - AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration); AddWaitStep("wait", 10); AddAssert("no score shown", () => !Player.IsScoreShown); } @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestStoryboardEndsBeforeCompletion() { CreateTest(() => AddStep("set storyboard duration to .1s", () => currentStoryboardDuration = 100)); - AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("skip overlay content not visible", () => fadeContainer().State == Visibility.Hidden); AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible); - AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); + AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 706d493fd6..a2e3ab7318 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -451,7 +451,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } private void checkPaused(int userId, bool state) - => AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state); + => AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().IsRunning != state); private void checkPausedInstant(int userId, bool state) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 269867be73..6098a3e794 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -671,7 +671,7 @@ namespace osu.Game.Tests.Visual.Multiplayer for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000) { double time = i; - AddUntilStep($"wait for time > {i}", () => this.ChildrenOfType().SingleOrDefault()?.GameplayClock.CurrentTime > time); + AddUntilStep($"wait for time > {i}", () => this.ChildrenOfType().SingleOrDefault()?.CurrentTime > time); } AddUntilStep("wait for results", () => multiplayerComponents.CurrentScreen is ResultsScreen); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 5cd9e0ddf9..7ed0be50e5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate for (int i = 0; i < Users.Count; i++) { - grid.Add(instances[i] = new PlayerArea(Users[i], masterClockContainer.GameplayClock)); + grid.Add(instances[i] = new PlayerArea(Users[i], masterClockContainer)); syncManager.AddPlayerClock(instances[i].GameplayClock); } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ffecb1d9a5..2cc1fe8eaf 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -1,11 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -16,13 +15,8 @@ namespace osu.Game.Screens.Play /// /// Encapsulates gameplay timing logic and provides a via DI for gameplay components to use. /// - public class GameplayClockContainer : Container, IAdjustableClock + public class GameplayClockContainer : Container, IAdjustableClock, IFrameBasedClock { - /// - /// The final clock which is exposed to gameplay components. - /// - public GameplayClock GameplayClock { get; private set; } - /// /// Whether gameplay is paused. /// @@ -41,7 +35,7 @@ namespace osu.Game.Screens.Play /// /// Invoked when a seek has been performed via /// - public event Action OnSeek; + public event Action? OnSeek; private double? startTime; @@ -59,11 +53,16 @@ namespace osu.Game.Screens.Play { startTime = value; - if (GameplayClock != null) + if (GameplayClock.IsNotNull()) GameplayClock.StartTime = value; } } + /// + /// The final clock which is exposed to gameplay components. + /// + protected GameplayClock GameplayClock { get; private set; } = null!; + /// /// Creates a new . /// @@ -215,12 +214,23 @@ namespace osu.Game.Screens.Play set => throw new NotSupportedException(); } - double IClock.Rate => GameplayClock.Rate; + public double Rate => GameplayClock.Rate; public double CurrentTime => GameplayClock.CurrentTime; public bool IsRunning => GameplayClock.IsRunning; #endregion + + public void ProcessFrame() + { + // Handled via update. Don't process here to safeguard from external usages potentially processing frames additional times. + } + + public double ElapsedFrameTime => GameplayClock.ElapsedFrameTime; + + public double FramesPerSecond => GameplayClock.FramesPerSecond; + + public FrameTimeInfo TimeInfo => GameplayClock.TimeInfo; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 08b6da1921..0ef09e4029 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -475,7 +475,7 @@ namespace osu.Game.Screens.Play private void updateSampleDisabledState() { - samplePlaybackDisabled.Value = DrawableRuleset.FrameStableClock.IsCatchingUp.Value || GameplayClockContainer.GameplayClock.IsPaused.Value; + samplePlaybackDisabled.Value = DrawableRuleset.FrameStableClock.IsCatchingUp.Value || GameplayClockContainer.IsPaused.Value; } private void updatePauseOnFocusLostState() @@ -877,7 +877,7 @@ namespace osu.Game.Screens.Play private double? lastPauseActionTime; protected bool PauseCooldownActive => - lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; + lastPauseActionTime.HasValue && GameplayClockContainer.CurrentTime < lastPauseActionTime + pause_cooldown; /// /// A set of conditionals which defines whether the current game state and configuration allows for @@ -915,7 +915,7 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Stop(); PauseOverlay.Show(); - lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; + lastPauseActionTime = GameplayClockContainer.CurrentTime; return true; } @@ -1005,7 +1005,7 @@ namespace osu.Game.Screens.Play /// protected virtual void StartGameplay() { - if (GameplayClockContainer.GameplayClock.IsRunning) + if (GameplayClockContainer.IsRunning) throw new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running"); GameplayClockContainer.Reset(true);