diff --git a/osu.Game.Tests/NonVisual/GameplayClockTest.cs b/osu.Game.Tests/NonVisual/GameplayClockTest.cs index 5b8aacd281..162734f9da 100644 --- a/osu.Game.Tests/NonVisual/GameplayClockTest.cs +++ b/osu.Game.Tests/NonVisual/GameplayClockTest.cs @@ -4,6 +4,7 @@ #nullable disable using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Timing; @@ -30,7 +31,7 @@ namespace osu.Game.Tests.NonVisual { public List> MutableNonGameplayAdjustments { get; } = new List>(); - public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + public override IEnumerable NonGameplayAdjustments => MutableNonGameplayAdjustments.Select(b => b.Value); public TestGameplayClock(IFrameBasedClock underlyingClock) : base(underlyingClock) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index a42e86933f..0aa412a4fd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -363,7 +363,7 @@ namespace osu.Game.Tests.Visual.Gameplay private Player player => Stack.CurrentScreen as Player; private double currentFrameStableTime - => player.ChildrenOfType().First().FrameStableClock.CurrentTime; + => player.ChildrenOfType().First().CurrentTime; private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index f7f62d2af0..59c1146995 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.UI public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both }; - public override IFrameStableClock FrameStableClock => frameStabilityContainer.FrameStableClock; + public override IFrameStableClock FrameStableClock => frameStabilityContainer; private bool frameStablePlayback = true; diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index bfad22b4f0..7b7003302a 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -1,8 +1,6 @@ // 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 System.Collections.Generic; using System.Diagnostics; @@ -21,7 +19,9 @@ namespace osu.Game.Rulesets.UI /// A container which consumes a parent gameplay clock and standardises frame counts for children. /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks. /// - public class FrameStabilityContainer : Container, IHasReplayHandler + [Cached(typeof(IGameplayClock))] + [Cached(typeof(IFrameStableClock))] + public class FrameStabilityContainer : Container, IHasReplayHandler, IFrameStableClock, IGameplayClock { private readonly double gameplayStartTime; @@ -35,16 +35,17 @@ namespace osu.Game.Rulesets.UI /// internal bool FrameStablePlayback = true; - public IFrameStableClock FrameStableClock => frameStableClock; + public readonly Bindable IsCatchingUp = new Bindable(); - [Cached(typeof(IGameplayClock))] - private readonly FrameStabilityClock frameStableClock; + public readonly Bindable WaitingOnFrames = new Bindable(); + + public IBindable IsPaused { get; } = new BindableBool(); public FrameStabilityContainer(double gameplayStartTime = double.MinValue) { RelativeSizeAxes = Axes.Both; - frameStableClock = new FrameStabilityClock(framedClock = new FramedClock(manualClock = new ManualClock())); + framedClock = new FramedClock(manualClock = new ManualClock()); this.gameplayStartTime = gameplayStartTime; } @@ -53,7 +54,7 @@ namespace osu.Game.Rulesets.UI private readonly FramedClock framedClock; - private IFrameBasedClock parentGameplayClock; + private IGameplayClock? parentGameplayClock; /// /// The current direction of playback to be exposed to frame stable children. @@ -63,13 +64,13 @@ namespace osu.Game.Rulesets.UI /// private int direction = 1; - [BackgroundDependencyLoader(true)] - private void load(IGameplayClock clock) + [BackgroundDependencyLoader] + private void load(IGameplayClock? clock) { if (clock != null) { - parentGameplayClock = frameStableClock.ParentGameplayClock = clock; - ((IBindable)frameStableClock.IsPaused).BindTo(clock.IsPaused); + parentGameplayClock = clock; + IsPaused.BindTo(parentGameplayClock.IsPaused); } } @@ -111,12 +112,12 @@ namespace osu.Game.Rulesets.UI private void updateClock() { - if (frameStableClock.WaitingOnFrames.Value) + if (WaitingOnFrames.Value) { // if waiting on frames, run one update loop to determine if frames have arrived. state = PlaybackState.Valid; } - else if (frameStableClock.IsPaused.Value) + else if (IsPaused.Value) { // time should not advance while paused, nor should anything run. state = PlaybackState.NotValid; @@ -154,8 +155,8 @@ namespace osu.Game.Rulesets.UI double timeBehind = Math.Abs(proposedTime - parentGameplayClock.CurrentTime); - frameStableClock.IsCatchingUp.Value = timeBehind > 200; - frameStableClock.WaitingOnFrames.Value = state == PlaybackState.NotValid; + IsCatchingUp.Value = timeBehind > 200; + WaitingOnFrames.Value = state == PlaybackState.NotValid; manualClock.CurrentTime = proposedTime; manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; @@ -177,6 +178,8 @@ namespace osu.Game.Rulesets.UI /// Whether playback is still valid. private bool updateReplay(ref double proposedTime) { + Debug.Assert(ReplayInputHandler != null); + double? newTime; if (FrameStablePlayback) @@ -238,18 +241,39 @@ namespace osu.Game.Rulesets.UI private void setClock() { - if (parentGameplayClock == null) - { - // in case a parent gameplay clock isn't available, just use the parent clock. - parentGameplayClock ??= Clock; - } - else - { - Clock = frameStableClock; - } + if (parentGameplayClock != null) + Clock = this; } - public ReplayInputHandler ReplayInputHandler { get; set; } + public ReplayInputHandler? ReplayInputHandler { get; set; } + + #region Delegation of IFrameStableClock + + public double CurrentTime => framedClock.CurrentTime; + + public double Rate => framedClock.Rate; + + public bool IsRunning => framedClock.IsRunning; + + public void ProcessFrame() => framedClock.ProcessFrame(); + + public double ElapsedFrameTime => framedClock.ElapsedFrameTime; + + public double FramesPerSecond => framedClock.FramesPerSecond; + + public FrameTimeInfo TimeInfo => framedClock.TimeInfo; + + #endregion + + #region Delegation of IGameplayClock + + public double TrueGameplayRate => parentGameplayClock?.TrueGameplayRate ?? 1; + + public double? StartTime => parentGameplayClock?.StartTime; + + public IEnumerable NonGameplayAdjustments => parentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty(); + + #endregion private enum PlaybackState { @@ -270,24 +294,7 @@ namespace osu.Game.Rulesets.UI Valid } - private class FrameStabilityClock : GameplayClock, IFrameStableClock - { - public IGameplayClock ParentGameplayClock; - - public readonly Bindable IsCatchingUp = new Bindable(); - - public readonly Bindable WaitingOnFrames = new Bindable(); - - public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); - - public FrameStabilityClock(FramedClock underlyingClock) - : base(underlyingClock) - { - } - - IBindable IFrameStableClock.IsCatchingUp => IsCatchingUp; - - IBindable IFrameStableClock.WaitingOnFrames => WaitingOnFrames; - } + IBindable IFrameStableClock.IsCatchingUp => IsCatchingUp; + IBindable IFrameStableClock.WaitingOnFrames => WaitingOnFrames; } } diff --git a/osu.Game/Rulesets/UI/IFrameStableClock.cs b/osu.Game/Rulesets/UI/IFrameStableClock.cs index 132605adaf..569ef5e06c 100644 --- a/osu.Game/Rulesets/UI/IFrameStableClock.cs +++ b/osu.Game/Rulesets/UI/IFrameStableClock.cs @@ -1,8 +1,6 @@ // 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 osu.Framework.Bindables; using osu.Framework.Timing; diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 454229fb31..b650922173 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play IBindable IGameplayClock.IsPaused => IsPaused; - public virtual IEnumerable> NonGameplayAdjustments => Enumerable.Empty>(); + public virtual IEnumerable NonGameplayAdjustments => Enumerable.Empty(); public GameplayClock(IFrameBasedClock underlyingClock) { @@ -46,12 +46,12 @@ namespace osu.Game.Screens.Play { double baseRate = Rate; - foreach (var adjustment in NonGameplayAdjustments) + foreach (double adjustment in NonGameplayAdjustments) { - if (Precision.AlmostEquals(adjustment.Value, 0)) + if (Precision.AlmostEquals(adjustment, 0)) return 0; - baseRate /= adjustment.Value; + baseRate /= adjustment; } return baseRate; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index df1eb32f99..27b37094ad 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Play } } - public IEnumerable> NonGameplayAdjustments => GameplayClock.NonGameplayAdjustments; + public IEnumerable NonGameplayAdjustments => GameplayClock.NonGameplayAdjustments; /// /// The final clock which is exposed to gameplay components. diff --git a/osu.Game/Screens/Play/IGameplayClock.cs b/osu.Game/Screens/Play/IGameplayClock.cs index c3d61be5d5..5f54ce691a 100644 --- a/osu.Game/Screens/Play/IGameplayClock.cs +++ b/osu.Game/Screens/Play/IGameplayClock.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play /// /// All adjustments applied to this clock which don't come from gameplay or mods. /// - IEnumerable> NonGameplayAdjustments { get; } + IEnumerable NonGameplayAdjustments { get; } IBindable IsPaused { get; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index d7f6992fee..587d2d40a1 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -303,7 +303,7 @@ namespace osu.Game.Screens.Play private class MasterGameplayClock : GameplayClock { public readonly List> MutableNonGameplayAdjustments = new List>(); - public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + public override IEnumerable NonGameplayAdjustments => MutableNonGameplayAdjustments.Select(b => b.Value); public MasterGameplayClock(FramedOffsetClock underlyingClock) : base(underlyingClock)