diff --git a/osu-framework b/osu-framework index e8ae207769..71900dc350 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit e8ae207769ec26fb7ddd67a2433514fcda354ecd +Subproject commit 71900dc350bcebbb60d912d4023a1d2a6bbbc3c1 diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index f465d0e202..3f8a17e23d 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -91,8 +91,6 @@ namespace osu.Game.Rulesets.UI #region Clock control - protected override bool ShouldProcessClock => false; // We handle processing the clock ourselves - private ManualClock clock; private IFrameBasedClock parentClock; @@ -103,6 +101,7 @@ namespace osu.Game.Rulesets.UI //our clock will now be our parent's clock, but we want to replace this to allow manual control. parentClock = Clock; + ProcessCustomClock = false; Clock = new FramedClock(clock = new ManualClock { CurrentTime = parentClock.CurrentTime, diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index e68a17f014..b0fbde74d2 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play private static bool hasShownNotificationOnce; - public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, DecoupleableInterpolatingFramedClock decoupledClock, WorkingBeatmap working, IAdjustableClock adjustableSourceClock) + public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IClock offsetClock, IAdjustableClock adjustableClock) { RelativeSizeAxes = Axes.Both; @@ -66,13 +66,13 @@ namespace osu.Game.Screens.Play BindRulesetContainer(rulesetContainer); Progress.Objects = rulesetContainer.Objects; - Progress.AudioClock = decoupledClock; + Progress.AudioClock = offsetClock; Progress.AllowSeeking = rulesetContainer.HasReplayLoaded; - Progress.OnSeek = pos => decoupledClock.Seek(pos); + Progress.OnSeek = pos => adjustableClock.Seek(pos); ModDisplay.Current.BindTo(working.Mods); - PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableSourceClock; + PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableClock; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index 669bcd600c..40e734b7df 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -44,14 +44,22 @@ namespace osu.Game.Screens.Play public Action OnResume; public Action OnPause; - public IAdjustableClock AudioClock; - public FramedClock FramedClock; + private readonly IAdjustableClock adjustableClock; + private readonly FramedClock framedClock; - public PauseContainer() + public PauseContainer(FramedClock framedClock, IAdjustableClock adjustableClock) { + this.framedClock = framedClock; + this.adjustableClock = adjustableClock; + RelativeSizeAxes = Axes.Both; - AddInternal(content = new Container { RelativeSizeAxes = Axes.Both }); + AddInternal(content = new Container + { + Clock = this.framedClock, + ProcessCustomClock = false, + RelativeSizeAxes = Axes.Both + }); AddInternal(pauseOverlay = new PauseOverlay { @@ -65,47 +73,37 @@ namespace osu.Game.Screens.Play }); } - public void Pause(bool force = false) + public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called. { if (!CanPause && !force) return; if (IsPaused) return; - // stop the decoupled clock (stops the audio eventually) - AudioClock.Stop(); - - // stop processing updatess on the offset clock (instantly freezes time for all our components) - FramedClock.ProcessSourceClockFrames = false; - + // stop the seekable clock (stops the audio eventually) + adjustableClock.Stop(); IsPaused = true; - // we need to do a final check after all of our children have processed up to the paused clock time. - // this is to cover cases where, for instance, the player fails in the current processing frame. - Schedule(() => - { - if (!CanPause) return; + OnPause?.Invoke(); + pauseOverlay.Show(); - lastPauseActionTime = Time.Current; - - OnPause?.Invoke(); - pauseOverlay.Show(); - }); - } + lastPauseActionTime = Time.Current; + }); public void Resume() { if (!IsPaused) return; IsPaused = false; - FramedClock.ProcessSourceClockFrames = true; - + IsResuming = false; lastPauseActionTime = Time.Current; - OnResume?.Invoke(); + // seek back to the time of the framed clock. + // this accounts for the audio clock potentially taking time to enter a completely stopped state. + adjustableClock.Seek(framedClock.CurrentTime); + adjustableClock.Start(); + OnResume?.Invoke(); pauseOverlay.Hide(); - AudioClock.Start(); - IsResuming = false; } private OsuGameBase game; @@ -122,6 +120,9 @@ namespace osu.Game.Screens.Play if (!game.IsActive && CanPause) Pause(); + if (!IsPaused) + framedClock.ProcessFrame(); + base.Update(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7a0c723ab5..ad803ebc44 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -56,9 +56,12 @@ namespace osu.Game.Screens.Play public CursorContainer Cursor => RulesetContainer.Cursor; public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value; - private IAdjustableClock adjustableSourceClock; - private FramedOffsetClock offsetClock; - private DecoupleableInterpolatingFramedClock decoupledClock; + private IAdjustableClock sourceClock; + + /// + /// The decoupled clock used for gameplay. Should be used for seeks and clock control. + /// + private DecoupleableInterpolatingFramedClock adjustableClock; private PauseContainer pauseContainer; @@ -140,17 +143,18 @@ namespace osu.Game.Screens.Play return; } - adjustableSourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); - decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); + adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; var firstObjectTime = RulesetContainer.Objects.First().StartTime; - decoupledClock.Seek(AllowLeadIn + adjustableClock.Seek(AllowLeadIn ? Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn)) : firstObjectTime); - decoupledClock.ProcessFrame(); + adjustableClock.ProcessFrame(); - offsetClock = new FramedOffsetClock(decoupledClock); + // the final usable gameplay clock with user-set offsets applied. + var offsetClock = new FramedOffsetClock(adjustableClock); userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.ValueChanged += v => offsetClock.Offset = v; @@ -160,16 +164,8 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { - storyboardContainer = new Container + pauseContainer = new PauseContainer(offsetClock, adjustableClock) { - RelativeSizeAxes = Axes.Both, - Clock = offsetClock, - Alpha = 0, - }, - pauseContainer = new PauseContainer - { - AudioClock = decoupledClock, - FramedClock = offsetClock, OnRetry = Restart, OnQuit = Exit, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, @@ -181,15 +177,23 @@ namespace osu.Game.Screens.Play OnResume = () => hudOverlay.KeyCounter.IsCounting = true, Children = new Drawable[] { - new Container + storyboardContainer = new Container { RelativeSizeAxes = Axes.Both, - Clock = offsetClock, - Child = RulesetContainer, + Alpha = 0, }, - new SkipButton(firstObjectTime) { AudioClock = decoupledClock }, - hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, decoupledClock, working, adjustableSourceClock) + RulesetContainer, + new SkipButton(firstObjectTime) { + Clock = Clock, // skip button doesn't want to use the audio clock directly + ProcessCustomClock = false, + AdjustableClock = adjustableClock, + FramedClock = offsetClock, + }, + hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) + { + Clock = Clock, // hud overlay doesn't want to use the audio clock directly + ProcessCustomClock = false, Anchor = Anchor.Centre, Origin = Anchor.Centre }, @@ -197,7 +201,7 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Clock = decoupledClock, + ProcessCustomClock = false, Breaks = beatmap.Breaks } } @@ -234,11 +238,11 @@ namespace osu.Game.Screens.Play private void applyRateFromMods() { - if (adjustableSourceClock == null) return; + if (sourceClock == null) return; - adjustableSourceClock.Rate = 1; + sourceClock.Rate = 1; foreach (var mod in Beatmap.Value.Mods.Value.OfType()) - mod.ApplyToClock(adjustableSourceClock); + mod.ApplyToClock(sourceClock); } private void initializeStoryboard(bool asyncLoad) @@ -297,7 +301,7 @@ namespace osu.Game.Screens.Play if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; - decoupledClock.Stop(); + adjustableClock.Stop(); HasFailed = true; failOverlay.Retries = RestartCount; @@ -326,17 +330,19 @@ namespace osu.Game.Screens.Play Task.Run(() => { - adjustableSourceClock.Reset(); + sourceClock.Reset(); Schedule(() => { - decoupledClock.ChangeSource(adjustableSourceClock); + adjustableClock.ChangeSource(sourceClock); applyRateFromMods(); this.Delay(750).Schedule(() => { if (!pauseContainer.IsPaused) - decoupledClock.Start(); + { + adjustableClock.Start(); + } }); }); }); @@ -363,9 +369,7 @@ namespace osu.Game.Screens.Play } if (loadedSuccessfully) - { pauseContainer?.Pause(); - } return true; } diff --git a/osu.Game/Screens/Play/SkipButton.cs b/osu.Game/Screens/Play/SkipButton.cs index f67a9b801e..08bb26c72b 100644 --- a/osu.Game/Screens/Play/SkipButton.cs +++ b/osu.Game/Screens/Play/SkipButton.cs @@ -24,7 +24,9 @@ namespace osu.Game.Screens.Play public class SkipButton : OverlayContainer, IKeyBindingHandler { private readonly double startTime; - public IAdjustableClock AudioClock; + + public IAdjustableClock AdjustableClock; + public IFrameBasedClock FramedClock; private Button button; private Box remainingTimeBox; @@ -60,8 +62,11 @@ namespace osu.Game.Screens.Play { var baseClock = Clock; - if (AudioClock != null) - Clock = new FramedClock(AudioClock) { ProcessSourceClockFrames = false }; + if (FramedClock != null) + { + Clock = FramedClock; + ProcessCustomClock = false; + } Children = new Drawable[] { @@ -109,7 +114,7 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => AudioClock?.Seek(startTime - skip_required_cutoff - fade_time); + button.Action = () => AdjustableClock?.Seek(startTime - skip_required_cutoff - fade_time); displayTime = Time.Current;