From 01f1018d02542cf2931aca603d6d8bae37b19a11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Mar 2019 13:26:54 +0900 Subject: [PATCH 01/11] Tidy up clock logic using DI and a GameplayClock --- osu.Game.Tests/Visual/TestCaseSongProgress.cs | 18 ++++- osu.Game/Rulesets/UI/Playfield.cs | 7 +- osu.Game/Screens/Play/BreakOverlay.cs | 17 +++-- osu.Game/Screens/Play/HUDOverlay.cs | 10 ++- osu.Game/Screens/Play/KeyCounter.cs | 7 +- osu.Game/Screens/Play/KeyCounterCollection.cs | 6 -- osu.Game/Screens/Play/PauseContainer.cs | 68 +++++++++++++++---- osu.Game/Screens/Play/Player.cs | 10 +-- osu.Game/Screens/Play/SkipOverlay.cs | 17 ++--- osu.Game/Screens/Play/SongProgress.cs | 32 ++++----- osu.Game/Screens/Play/SongProgressInfo.cs | 18 +++-- 11 files changed, 127 insertions(+), 83 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseSongProgress.cs b/osu.Game.Tests/Visual/TestCaseSongProgress.cs index 9845df7461..cdb1cd2286 100644 --- a/osu.Game.Tests/Visual/TestCaseSongProgress.cs +++ b/osu.Game.Tests/Visual/TestCaseSongProgress.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.MathUtils; using osu.Framework.Timing; @@ -19,14 +20,20 @@ namespace osu.Game.Tests.Visual private readonly StopwatchClock clock; + [Cached] + private readonly GameplayClock gameplayClock; + + private readonly FramedClock framedClock; + public TestCaseSongProgress() { clock = new StopwatchClock(true); + gameplayClock = new GameplayClock(framedClock = new FramedClock(clock)); + Add(progress = new SongProgress { RelativeSizeAxes = Axes.X, - AudioClock = new StopwatchClock(true), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, }); @@ -68,8 +75,13 @@ namespace osu.Game.Tests.Visual progress.Objects = objects; graph.Objects = objects; - progress.AudioClock = clock; - progress.OnSeek = pos => clock.Seek(pos); + progress.RequestSeek = pos => clock.Seek(pos); + } + + protected override void Update() + { + base.Update(); + framedClock.ProcessFrame(); } private class TestSongProgressGraph : SongProgressGraph diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 3b8a7353c6..7b8e044461 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -12,6 +12,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.UI @@ -59,10 +60,12 @@ namespace osu.Game.Rulesets.UI private WorkingBeatmap beatmap; - [BackgroundDependencyLoader] - private void load(IBindable beatmap) + [BackgroundDependencyLoader(true)] + private void load(IBindable beatmap, GameplayClock clock) { this.beatmap = beatmap.Value; + + if (clock != null) Clock = clock; } /// diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 024ce01dc6..d390787090 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -40,13 +41,7 @@ namespace osu.Game.Screens.Play private readonly BreakInfo info; private readonly BreakArrows breakArrows; - public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) - : this(letterboxing) - { - bindProcessor(scoreProcessor); - } - - public BreakOverlay(bool letterboxing) + public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null) { RelativeSizeAxes = Axes.Both; Child = fadeContainer = new Container @@ -98,6 +93,14 @@ namespace osu.Game.Screens.Play } } }; + + if (scoreProcessor != null) bindProcessor(scoreProcessor); + } + + [BackgroundDependencyLoader(true)] + private void load(GameplayClock clock) + { + if (clock != null) Clock = clock; } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 130d2ecc95..a19b0d1e5c 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play private static bool hasShownNotificationOnce; - public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IClock offsetClock, IAdjustableClock adjustableClock) + public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IAdjustableClock adjustableClock) { RelativeSizeAxes = Axes.Both; @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Vertical, Children = new Drawable[] { - KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock), + KeyCounter = CreateKeyCounter(), HoldToQuit = CreateHoldForMenuButton(), } } @@ -91,9 +91,8 @@ namespace osu.Game.Screens.Play BindRulesetContainer(rulesetContainer); Progress.Objects = rulesetContainer.Objects; - Progress.AudioClock = offsetClock; Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value; - Progress.OnSeek = pos => adjustableClock.Seek(pos); + Progress.RequestSeek = pos => adjustableClock.Seek(pos); ModDisplay.Current.BindTo(working.Mods); @@ -202,13 +201,12 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 } }; - protected virtual KeyCounterCollection CreateKeyCounter(IFrameBasedClock offsetClock) => new KeyCounterCollection + protected virtual KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection { FadeTime = 50, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Margin = new MarginPadding(10), - AudioClock = offsetClock }; protected virtual SongProgress CreateProgress() => new SongProgress diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 0e1f938137..0626c40334 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -71,9 +71,12 @@ namespace osu.Game.Screens.Play Name = name; } - [BackgroundDependencyLoader] - private void load(TextureStore textures) + [BackgroundDependencyLoader(true)] + private void load(TextureStore textures, GameplayClock clock) { + if (clock != null) + Clock = clock; + Children = new Drawable[] { buttonSprite = new Sprite diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index 0259258636..1b43737731 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -8,7 +8,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Configuration; using osuTK; using osuTK.Graphics; @@ -37,9 +36,6 @@ namespace osu.Game.Screens.Play key.FadeTime = FadeTime; key.KeyDownTextColor = KeyDownTextColor; key.KeyUpTextColor = KeyUpTextColor; - // Use the same clock object as SongProgress for saving KeyCounter state - if (AudioClock != null) - key.Clock = AudioClock; } public void ResetCount() @@ -125,8 +121,6 @@ namespace osu.Game.Screens.Play public override bool HandleNonPositionalInput => receptor == null; public override bool HandlePositionalInput => receptor == null; - public IFrameBasedClock AudioClock { get; set; } - private Receptor receptor; public Receptor GetReceptor() diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index 8961d91763..c12e5227c8 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -43,24 +43,32 @@ namespace osu.Game.Screens.Play public Action OnRetry; public Action OnQuit; - private readonly FramedClock framedClock; - private readonly DecoupleableInterpolatingFramedClock decoupledClock; + private readonly FramedClock offsetClock; + private readonly DecoupleableInterpolatingFramedClock adjustableClock; + + /// + /// The final clock which is exposed to underlying components. + /// + [Cached] + private readonly GameplayClock gameplayClock; /// /// Creates a new . /// - /// The gameplay clock. This is the clock that will process frames. - /// The seekable clock. This is the clock that will be paused and resumed. - public PauseContainer(FramedClock framedClock, DecoupleableInterpolatingFramedClock decoupledClock) + /// The gameplay clock. This is the clock that will process frames. Includes user/system offsets. + /// The seekable clock. This is the clock that will be paused and resumed. Should not be processed (it is processed automatically by ). + public PauseContainer(FramedClock offsetClock, DecoupleableInterpolatingFramedClock adjustableClock) { - this.framedClock = framedClock; - this.decoupledClock = decoupledClock; + this.offsetClock = offsetClock; + this.adjustableClock = adjustableClock; + + gameplayClock = new GameplayClock(offsetClock); RelativeSizeAxes = Axes.Both; AddInternal(content = new Container { - Clock = this.framedClock, + Clock = this.offsetClock, ProcessCustomClock = false, RelativeSizeAxes = Axes.Both }); @@ -84,7 +92,7 @@ namespace osu.Game.Screens.Play if (IsPaused.Value) return; // stop the seekable clock (stops the audio eventually) - decoupledClock.Stop(); + adjustableClock.Stop(); IsPaused.Value = true; pauseOverlay.Show(); @@ -102,8 +110,8 @@ namespace osu.Game.Screens.Play // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time // This accounts for the audio clock source potentially taking time to enter a completely stopped state - decoupledClock.Seek(decoupledClock.CurrentTime); - decoupledClock.Start(); + adjustableClock.Seek(adjustableClock.CurrentTime); + adjustableClock.Start(); pauseOverlay.Hide(); } @@ -123,7 +131,7 @@ namespace osu.Game.Screens.Play Pause(); if (!IsPaused.Value) - framedClock.ProcessFrame(); + offsetClock.ProcessFrame(); base.Update(); } @@ -146,4 +154,40 @@ namespace osu.Game.Screens.Play } } } + + /// + /// A clock which is used for gameplay elements that need to follow audio time 1:1. + /// Exposed via DI by . + /// + /// THe main purpose of this clock is to stop components using it from accidentally processing the main + /// , as this should only be done once to ensure accuracy. + /// + /// + public class GameplayClock : IFrameBasedClock + { + private readonly IFrameBasedClock underlyingClock; + + public GameplayClock(IFrameBasedClock underlyingClock) + { + this.underlyingClock = underlyingClock; + } + + public double CurrentTime => underlyingClock.CurrentTime; + + public double Rate => underlyingClock.Rate; + + public bool IsRunning => underlyingClock.IsRunning; + + public void ProcessFrame() + { + // we do not want to process the underlying clock. + // this is handled by PauseContainer. + } + + public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; + + public double FramesPerSecond => underlyingClock.FramesPerSecond; + + public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; + } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bb2211a533..53a86aaf0f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -196,26 +196,20 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.Centre, Origin = Anchor.Centre, - ProcessCustomClock = false, Breaks = beatmap.Breaks }, new ScalingContainer(ScalingMode.Gameplay) { Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(), }, - HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) + HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, adjustableClock) { - Clock = Clock, // hud overlay doesn't want to use the audio clock directly - ProcessCustomClock = false, Anchor = Anchor.Centre, Origin = Anchor.Centre }, new SkipOverlay(RulesetContainer.GameplayStartTime) { - Clock = Clock, // skip button doesn't want to use the audio clock directly - ProcessCustomClock = false, - AdjustableClock = adjustableClock, - FramedClock = offsetClock, + RequestSeek = time => adjustableClock.Seek(time) }, } }, diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 010d9228de..a6e6009b95 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -9,7 +9,6 @@ using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; -using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Ranking; @@ -27,8 +26,7 @@ namespace osu.Game.Screens.Play { private readonly double startTime; - public IAdjustableClock AdjustableClock; - public IFrameBasedClock FramedClock; + public Action RequestSeek; private Button button; private Box remainingTimeBox; @@ -54,16 +52,13 @@ namespace osu.Game.Screens.Play Origin = Anchor.Centre; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, GameplayClock clock) { var baseClock = Clock; - if (FramedClock != null) - { - Clock = FramedClock; - ProcessCustomClock = false; - } + if (clock != null) + Clock = clock; Children = new Drawable[] { @@ -111,7 +106,7 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => AdjustableClock?.Seek(startTime - skip_required_cutoff - fade_time); + button.Action = () => RequestSeek?.Invoke(startTime - skip_required_cutoff - fade_time); displayTime = Time.Current; diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 3c7e3b2067..e3d6ca16a7 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -10,7 +10,6 @@ using osu.Game.Graphics; using osu.Framework.Allocation; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; @@ -29,18 +28,11 @@ namespace osu.Game.Screens.Play private readonly SongProgressGraph graph; private readonly SongProgressInfo info; - public Action OnSeek; + public Action RequestSeek; public override bool HandleNonPositionalInput => AllowSeeking; public override bool HandlePositionalInput => AllowSeeking; - private IClock audioClock; - - public IClock AudioClock - { - set => audioClock = info.AudioClock = value; - } - private double lastHitTime => ((objects.Last() as IHasEndTime)?.EndTime ?? objects.Last().StartTime) + 1; private double firstHitTime => objects.First().StartTime; @@ -63,9 +55,14 @@ namespace osu.Game.Screens.Play private readonly BindableBool replayLoaded = new BindableBool(); - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private GameplayClock gameplayClock; + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, GameplayClock clock) { + if (clock != null) + gameplayClock = clock; + graph.FillColour = bar.FillColour = colours.BlueLighter; } @@ -99,7 +96,7 @@ namespace osu.Game.Screens.Play Alpha = 0, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - OnSeek = position => OnSeek?.Invoke(position), + OnSeek = time => RequestSeek?.Invoke(time), }, }; } @@ -157,14 +154,11 @@ namespace osu.Game.Screens.Play if (objects == null) return; - double position = audioClock?.CurrentTime ?? Time.Current; - double progress = (position - firstHitTime) / (lastHitTime - firstHitTime); + double position = gameplayClock?.CurrentTime ?? Time.Current; + double progress = Math.Min(1, (position - firstHitTime) / (lastHitTime - firstHitTime)); - if (progress < 1) - { - bar.CurrentTime = position; - graph.Progress = (int)(graph.ColumnCount * progress); - } + bar.CurrentTime = position; + graph.Progress = (int)(graph.ColumnCount * progress); } } } diff --git a/osu.Game/Screens/Play/SongProgressInfo.cs b/osu.Game/Screens/Play/SongProgressInfo.cs index 369abb53c8..7441c335d2 100644 --- a/osu.Game/Screens/Play/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/SongProgressInfo.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using System; @@ -27,8 +26,6 @@ namespace osu.Game.Screens.Play private const int margin = 10; - public IClock AudioClock; - public double StartTime { set => startTime = value; @@ -39,9 +36,14 @@ namespace osu.Game.Screens.Play set => endTime = value; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private GameplayClock gameplayClock; + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, GameplayClock clock) { + if (clock != null) + gameplayClock = clock; + Children = new Drawable[] { timeCurrent = new OsuSpriteText @@ -80,7 +82,9 @@ namespace osu.Game.Screens.Play { base.Update(); - double songCurrentTime = AudioClock.CurrentTime - startTime; + var time = gameplayClock?.CurrentTime ?? Time.Current; + + double songCurrentTime = time - startTime; int currentPercent = Math.Max(0, Math.Min(100, (int)(songCurrentTime / songLength * 100))); int currentSecond = (int)Math.Floor(songCurrentTime / 1000.0); @@ -93,7 +97,7 @@ namespace osu.Game.Screens.Play if (currentSecond != previousSecond && songCurrentTime < songLength) { timeCurrent.Text = formatTime(TimeSpan.FromSeconds(currentSecond)); - timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime)); + timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - time)); previousSecond = currentSecond; } From c5cd9972c4a8f5d01090775f885683fd545541c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Mar 2019 13:27:39 +0900 Subject: [PATCH 02/11] Make restart private --- osu.Game/Screens/Play/Player.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 53a86aaf0f..28c2b6ff43 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -178,7 +178,7 @@ namespace osu.Game.Screens.Play PauseContainer = new PauseContainer(offsetClock, adjustableClock) { Retries = RestartCount, - OnRetry = Restart, + OnRetry = restart, OnQuit = performUserRequestedExit, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value, Children = new Container[] @@ -215,7 +215,7 @@ namespace osu.Game.Screens.Play }, failOverlay = new FailOverlay { - OnRetry = Restart, + OnRetry = restart, OnQuit = performUserRequestedExit, }, new HotkeyRetryOverlay @@ -225,7 +225,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; fadeOut(true); - Restart(); + restart(); }, } }; @@ -262,7 +262,7 @@ namespace osu.Game.Screens.Play this.Exit(); } - public void Restart() + private void restart() { if (!this.IsCurrentScreen()) return; From 4e33a98dbca57b722ae4e5d3dbb93ecbcb48e591 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Mar 2019 13:53:47 +0900 Subject: [PATCH 03/11] Move gameplay clock to own class --- osu.Game/Screens/Play/GameplayClock.cs | 43 +++++++++++++++++++++++++ osu.Game/Screens/Play/PauseContainer.cs | 36 --------------------- 2 files changed, 43 insertions(+), 36 deletions(-) create mode 100644 osu.Game/Screens/Play/GameplayClock.cs diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs new file mode 100644 index 0000000000..e7b80b6d76 --- /dev/null +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Timing; + +namespace osu.Game.Screens.Play +{ + /// + /// A clock which is used for gameplay elements that need to follow audio time 1:1. + /// Exposed via DI by . + /// + /// THe main purpose of this clock is to stop components using it from accidentally processing the main + /// , as this should only be done once to ensure accuracy. + /// + /// + public class GameplayClock : IFrameBasedClock + { + private readonly IFrameBasedClock underlyingClock; + + public GameplayClock(IFrameBasedClock underlyingClock) + { + this.underlyingClock = underlyingClock; + } + + public double CurrentTime => underlyingClock.CurrentTime; + + public double Rate => underlyingClock.Rate; + + public bool IsRunning => underlyingClock.IsRunning; + + public void ProcessFrame() + { + // we do not want to process the underlying clock. + // this is handled by PauseContainer. + } + + public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; + + public double FramesPerSecond => underlyingClock.FramesPerSecond; + + public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index c12e5227c8..da1d6ede5e 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -154,40 +154,4 @@ namespace osu.Game.Screens.Play } } } - - /// - /// A clock which is used for gameplay elements that need to follow audio time 1:1. - /// Exposed via DI by . - /// - /// THe main purpose of this clock is to stop components using it from accidentally processing the main - /// , as this should only be done once to ensure accuracy. - /// - /// - public class GameplayClock : IFrameBasedClock - { - private readonly IFrameBasedClock underlyingClock; - - public GameplayClock(IFrameBasedClock underlyingClock) - { - this.underlyingClock = underlyingClock; - } - - public double CurrentTime => underlyingClock.CurrentTime; - - public double Rate => underlyingClock.Rate; - - public bool IsRunning => underlyingClock.IsRunning; - - public void ProcessFrame() - { - // we do not want to process the underlying clock. - // this is handled by PauseContainer. - } - - public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; - - public double FramesPerSecond => underlyingClock.FramesPerSecond; - - public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; - } } From ec063a13db73aabc29a77b9c0b9ff189a1ed4a3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Mar 2019 16:34:50 +0900 Subject: [PATCH 04/11] Update RulesetInputManager to support new clock structure more accurately --- osu.Game/Rulesets/UI/Playfield.cs | 7 +-- osu.Game/Rulesets/UI/RulesetInputManager.cs | 70 ++++++++++++--------- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 7b8e044461..3b8a7353c6 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -12,7 +12,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.UI @@ -60,12 +59,10 @@ namespace osu.Game.Rulesets.UI private WorkingBeatmap beatmap; - [BackgroundDependencyLoader(true)] - private void load(IBindable beatmap, GameplayClock clock) + [BackgroundDependencyLoader] + private void load(IBindable beatmap) { this.beatmap = beatmap.Value; - - if (clock != null) Clock = clock; } /// diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index fc61d41ab4..d5c8f9a32f 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -41,6 +41,7 @@ namespace osu.Game.Rulesets.UI protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique); + gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); } #region Action mapping (for replays) @@ -86,22 +87,35 @@ namespace osu.Game.Rulesets.UI #region Clock control - private ManualClock clock; - private IFrameBasedClock parentClock; + private readonly ManualClock manualClock; + + private readonly FramedClock framedClock; + + [Cached] + private GameplayClock gameplayClock; + + private IFrameBasedClock parentGameplayClock; + + [BackgroundDependencyLoader(true)] + private void load(OsuConfigManager config, GameplayClock clock) + { + mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); + + if (clock != null) + parentGameplayClock = clock; + } + protected override void LoadComplete() { base.LoadComplete(); - //our clock will now be our parent's clock, but we want to replace this to allow manual control. - parentClock = Clock; + // in case a parent gameplay clock isn't available, just use the parent clock. + if (parentGameplayClock == null) + parentGameplayClock = Clock; + Clock = gameplayClock; ProcessCustomClock = false; - Clock = new FramedClock(clock = new ManualClock - { - CurrentTime = parentClock.CurrentTime, - Rate = parentClock.Rate, - }); } /// @@ -147,25 +161,31 @@ namespace osu.Game.Rulesets.UI private void updateClock() { - if (parentClock == null) return; + if (parentGameplayClock == null) + { + validState = false; + return; + } - clock.Rate = parentClock.Rate; - clock.IsRunning = parentClock.IsRunning; + validState = true; - var newProposedTime = parentClock.CurrentTime; + manualClock.Rate = parentGameplayClock.Rate; + manualClock.IsRunning = parentGameplayClock.IsRunning; + + var newProposedTime = parentGameplayClock.CurrentTime; try { - if (Math.Abs(clock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) { - newProposedTime = clock.Rate > 0 - ? Math.Min(newProposedTime, clock.CurrentTime + sixty_frame_time) - : Math.Max(newProposedTime, clock.CurrentTime - sixty_frame_time); + newProposedTime = manualClock.Rate > 0 + ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) + : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); } if (!isAttached) { - clock.CurrentTime = newProposedTime; + manualClock.CurrentTime = newProposedTime; } else { @@ -177,20 +197,20 @@ namespace osu.Game.Rulesets.UI validState = false; requireMoreUpdateLoops = true; - clock.CurrentTime = newProposedTime; + manualClock.CurrentTime = newProposedTime; return; } - clock.CurrentTime = newTime.Value; + manualClock.CurrentTime = newTime.Value; } - requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime; + requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime; } finally { // The manual clock time has changed in the above code. The framed clock now needs to be updated // to ensure that the its time is valid for our children before input is processed - Clock.ProcessFrame(); + framedClock.ProcessFrame(); } } @@ -200,12 +220,6 @@ namespace osu.Game.Rulesets.UI private Bindable mouseDisabled; - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); - } - protected override bool Handle(UIEvent e) { switch (e) From 9a9b4efb8dce5318d5f5cacb7ba0b365ca0d0662 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Mar 2019 16:41:11 +0900 Subject: [PATCH 05/11] Fix excess line --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index d5c8f9a32f..796b52d413 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -105,7 +105,6 @@ namespace osu.Game.Rulesets.UI parentGameplayClock = clock; } - protected override void LoadComplete() { base.LoadComplete(); From 8ad258f42676b053083d467e5cc0ddee080ebd4f Mon Sep 17 00:00:00 2001 From: LeNitrous Date: Tue, 5 Mar 2019 17:34:23 +0800 Subject: [PATCH 06/11] only check IsLoggedIn after requesting scores --- osu.Game/Online/Leaderboards/Leaderboard.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 38df0efd6f..2c19b939e7 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -213,12 +213,6 @@ namespace osu.Game.Online.Leaderboards pendingUpdateScores?.Cancel(); pendingUpdateScores = Schedule(() => { - if (api?.IsLoggedIn != true) - { - PlaceholderState = PlaceholderState.NotLoggedIn; - return; - } - PlaceholderState = PlaceholderState.Retrieving; loading.Show(); @@ -231,6 +225,12 @@ namespace osu.Game.Online.Leaderboards if (getScoresRequest == null) return; + if (api?.IsLoggedIn != true) + { + PlaceholderState = PlaceholderState.NotLoggedIn; + return; + } + getScoresRequest.Failure += e => Schedule(() => { if (e is OperationCanceledException) From f98d55531fa4a7cf57c9e8f07858c06ce367cf8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Mar 2019 18:48:59 +0900 Subject: [PATCH 07/11] Add documentation for FetchScores --- osu.Game/Online/Leaderboards/Leaderboard.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 2c19b939e7..f5b6e185c7 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -243,6 +243,11 @@ namespace osu.Game.Online.Leaderboards }); } + /// + /// Performs a fetch/refresh of scores to be displayed. + /// + /// A callback which should be called when fetching is completed. Scheduling is not required. + /// An responsible for the fetch operation. This will be queued and performed automatically. protected abstract APIRequest FetchScores(Action> scoresCallback); private Placeholder currentPlaceholder; From fafc80818cf5ec090e67d23210cf71ee9d5fc5e2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Tue, 5 Mar 2019 18:50:46 +0900 Subject: [PATCH 08/11] Fix typo Co-Authored-By: peppy --- osu.Game/Screens/Play/GameplayClock.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index e7b80b6d76..31345ca41e 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.Play /// A clock which is used for gameplay elements that need to follow audio time 1:1. /// Exposed via DI by . /// - /// THe main purpose of this clock is to stop components using it from accidentally processing the main + /// The main purpose of this clock is to stop components using it from accidentally processing the main /// , as this should only be done once to ensure accuracy. /// /// @@ -40,4 +40,4 @@ namespace osu.Game.Screens.Play public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; } -} \ No newline at end of file +} From 76ce3954a0e1f9da87c2fcab0b2e13beb5fb14a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Mar 2019 19:30:55 +0900 Subject: [PATCH 09/11] Rename PauseContainer --- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 6 +++--- .../Visual/TestCaseGameplayMenuOverlay.cs | 6 +++--- osu.Game/Screens/Play/GameplayClock.cs | 2 +- ...ntainer.cs => PausableGameplayContainer.cs} | 9 +++++---- osu.Game/Screens/Play/Player.cs | 18 +++++++++--------- 5 files changed, 21 insertions(+), 20 deletions(-) rename osu.Game/Screens/Play/{PauseContainer.cs => PausableGameplayContainer.cs} (93%) diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index d850c58f09..5484824c5b 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -188,10 +188,10 @@ namespace osu.Game.Tests.Visual public void PauseTest() { performFullSetup(true); - AddStep("Pause", () => player.CurrentPauseContainer.Pause()); + AddStep("Pause", () => player.CurrentPausableGameplayContainer.Pause()); waitForDim(); AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); - AddStep("Unpause", () => player.CurrentPauseContainer.Resume()); + AddStep("Unpause", () => player.CurrentPausableGameplayContainer.Resume()); waitForDim(); AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); } @@ -328,7 +328,7 @@ namespace osu.Game.Tests.Visual }; } - public PauseContainer CurrentPauseContainer => PauseContainer; + public PausableGameplayContainer CurrentPausableGameplayContainer => PausableGameplayContainer; public UserDimContainer CurrentStoryboardContainer => StoryboardContainer; diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs index a21573236a..93a059d214 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -17,15 +17,15 @@ namespace osu.Game.Tests.Visual [Description("player pause/fail screens")] public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PausableGameplayContainer) }; private FailOverlay failOverlay; - private PauseContainer.PauseOverlay pauseOverlay; + private PausableGameplayContainer.PauseOverlay pauseOverlay; [BackgroundDependencyLoader] private void load() { - Add(pauseOverlay = new PauseContainer.PauseOverlay + Add(pauseOverlay = new PausableGameplayContainer.PauseOverlay { OnResume = () => Logger.Log(@"Resume"), OnRetry = () => Logger.Log(@"Retry"), diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 31345ca41e..0400bfbc27 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -7,7 +7,7 @@ namespace osu.Game.Screens.Play { /// /// A clock which is used for gameplay elements that need to follow audio time 1:1. - /// Exposed via DI by . + /// Exposed via DI by . /// /// The main purpose of this clock is to stop components using it from accidentally processing the main /// , as this should only be done once to ensure accuracy. diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PausableGameplayContainer.cs similarity index 93% rename from osu.Game/Screens/Play/PauseContainer.cs rename to osu.Game/Screens/Play/PausableGameplayContainer.cs index da1d6ede5e..fa475deb34 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PausableGameplayContainer.cs @@ -14,10 +14,11 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play { /// - /// A container which handles pausing children, displaying a pause overlay with choices etc. + /// A container which handles pausing children, displaying a pause overlay with choices and processing the clock. + /// Exposes a to children via DI. /// This alleviates a lot of the intricate pause logic from being in /// - public class PauseContainer : Container + public class PausableGameplayContainer : Container { public readonly BindableBool IsPaused = new BindableBool(); @@ -53,11 +54,11 @@ namespace osu.Game.Screens.Play private readonly GameplayClock gameplayClock; /// - /// Creates a new . + /// Creates a new . /// /// The gameplay clock. This is the clock that will process frames. Includes user/system offsets. /// The seekable clock. This is the clock that will be paused and resumed. Should not be processed (it is processed automatically by ). - public PauseContainer(FramedClock offsetClock, DecoupleableInterpolatingFramedClock adjustableClock) + public PausableGameplayContainer(FramedClock offsetClock, DecoupleableInterpolatingFramedClock adjustableClock) { this.offsetClock = offsetClock; this.adjustableClock = adjustableClock; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 28c2b6ff43..44166115b1 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Play [Resolved] private ScoreManager scoreManager { get; set; } - protected PauseContainer PauseContainer { get; private set; } + protected PausableGameplayContainer PausableGameplayContainer { get; private set; } private RulesetInfo ruleset; @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Play InternalChildren = new Drawable[] { - PauseContainer = new PauseContainer(offsetClock, adjustableClock) + PausableGameplayContainer = new PausableGameplayContainer(offsetClock, adjustableClock) { Retries = RestartCount, OnRetry = restart, @@ -233,7 +233,7 @@ namespace osu.Game.Screens.Play HUDOverlay.HoldToQuit.Action = performUserRequestedExit; HUDOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded); - RulesetContainer.IsPaused.BindTo(PauseContainer.IsPaused); + RulesetContainer.IsPaused.BindTo(PausableGameplayContainer.IsPaused); if (ShowStoryboard.Value) initializeStoryboard(false); @@ -366,7 +366,7 @@ namespace osu.Game.Screens.Play this.Delay(750).Schedule(() => { - if (!PauseContainer.IsPaused.Value) + if (!PausableGameplayContainer.IsPaused.Value) { adjustableClock.Start(); } @@ -374,8 +374,8 @@ namespace osu.Game.Screens.Play }); }); - PauseContainer.Alpha = 0; - PauseContainer.FadeIn(750, Easing.OutQuint); + PausableGameplayContainer.Alpha = 0; + PausableGameplayContainer.FadeIn(750, Easing.OutQuint); } public override void OnSuspending(IScreen next) @@ -393,7 +393,7 @@ namespace osu.Game.Screens.Play return true; } - if ((!AllowPause || HasFailed || !ValidForResume || PauseContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PauseContainer?.IsResuming ?? true)) + if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true)) { // In the case of replays, we may have changed the playback rate. applyRateFromMods(); @@ -402,7 +402,7 @@ namespace osu.Game.Screens.Play } if (LoadedBeatmapSuccessfully) - PauseContainer?.Pause(); + PausableGameplayContainer?.Pause(); return true; } @@ -416,7 +416,7 @@ namespace osu.Game.Screens.Play storyboardReplacesBackground.Value = false; } - protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PauseContainer.IsPaused.Value; + protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PausableGameplayContainer.IsPaused.Value; private void initializeStoryboard(bool asyncLoad) { From d5943330b1b39453e2c27937a47a46eba4c6f448 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Mar 2019 20:06:04 +0900 Subject: [PATCH 10/11] Fix potential infinite loop --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 796b52d413..fe45941756 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -161,10 +161,7 @@ namespace osu.Game.Rulesets.UI private void updateClock() { if (parentGameplayClock == null) - { - validState = false; return; - } validState = true; From 558dbafb719006f550a184754b698175c10ca578 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Mar 2019 20:14:04 +0900 Subject: [PATCH 11/11] Use a safer method of setting the clock --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index fe45941756..87220a37e8 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -108,13 +108,7 @@ namespace osu.Game.Rulesets.UI protected override void LoadComplete() { base.LoadComplete(); - - // in case a parent gameplay clock isn't available, just use the parent clock. - if (parentGameplayClock == null) - parentGameplayClock = Clock; - - Clock = gameplayClock; - ProcessCustomClock = false; + setClock(); } /// @@ -161,7 +155,7 @@ namespace osu.Game.Rulesets.UI private void updateClock() { if (parentGameplayClock == null) - return; + setClock(); // LoadComplete may not be run yet, but we still want the clock. validState = true; @@ -210,6 +204,16 @@ namespace osu.Game.Rulesets.UI } } + private void setClock() + { + // in case a parent gameplay clock isn't available, just use the parent clock. + if (parentGameplayClock == null) + parentGameplayClock = Clock; + + Clock = gameplayClock; + ProcessCustomClock = false; + } + #endregion #region Setting application (disables etc.)