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;