1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 01:27:29 +08:00

Merge pull request #2126 from peppy/clock-fixes

Improve clock handling logic for gameplay
This commit is contained in:
Dan Balasescu 2018-03-05 17:55:10 +09:00 committed by GitHub
commit 0e2a7bf680
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 79 additions and 70 deletions

@ -1 +1 @@
Subproject commit e8ae207769ec26fb7ddd67a2433514fcda354ecd Subproject commit 71900dc350bcebbb60d912d4023a1d2a6bbbc3c1

View File

@ -91,8 +91,6 @@ namespace osu.Game.Rulesets.UI
#region Clock control #region Clock control
protected override bool ShouldProcessClock => false; // We handle processing the clock ourselves
private ManualClock clock; private ManualClock clock;
private IFrameBasedClock parentClock; 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. //our clock will now be our parent's clock, but we want to replace this to allow manual control.
parentClock = Clock; parentClock = Clock;
ProcessCustomClock = false;
Clock = new FramedClock(clock = new ManualClock Clock = new FramedClock(clock = new ManualClock
{ {
CurrentTime = parentClock.CurrentTime, CurrentTime = parentClock.CurrentTime,

View File

@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play
private static bool hasShownNotificationOnce; 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; RelativeSizeAxes = Axes.Both;
@ -66,13 +66,13 @@ namespace osu.Game.Screens.Play
BindRulesetContainer(rulesetContainer); BindRulesetContainer(rulesetContainer);
Progress.Objects = rulesetContainer.Objects; Progress.Objects = rulesetContainer.Objects;
Progress.AudioClock = decoupledClock; Progress.AudioClock = offsetClock;
Progress.AllowSeeking = rulesetContainer.HasReplayLoaded; Progress.AllowSeeking = rulesetContainer.HasReplayLoaded;
Progress.OnSeek = pos => decoupledClock.Seek(pos); Progress.OnSeek = pos => adjustableClock.Seek(pos);
ModDisplay.Current.BindTo(working.Mods); ModDisplay.Current.BindTo(working.Mods);
PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableSourceClock; PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableClock;
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]

View File

@ -44,14 +44,22 @@ namespace osu.Game.Screens.Play
public Action OnResume; public Action OnResume;
public Action OnPause; public Action OnPause;
public IAdjustableClock AudioClock; private readonly IAdjustableClock adjustableClock;
public FramedClock FramedClock; private readonly FramedClock framedClock;
public PauseContainer() public PauseContainer(FramedClock framedClock, IAdjustableClock adjustableClock)
{ {
this.framedClock = framedClock;
this.adjustableClock = adjustableClock;
RelativeSizeAxes = Axes.Both; 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 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 (!CanPause && !force) return;
if (IsPaused) return; if (IsPaused) return;
// stop the decoupled clock (stops the audio eventually) // stop the seekable clock (stops the audio eventually)
AudioClock.Stop(); adjustableClock.Stop();
// stop processing updatess on the offset clock (instantly freezes time for all our components)
FramedClock.ProcessSourceClockFrames = false;
IsPaused = true; IsPaused = true;
// we need to do a final check after all of our children have processed up to the paused clock time. OnPause?.Invoke();
// this is to cover cases where, for instance, the player fails in the current processing frame. pauseOverlay.Show();
Schedule(() =>
{
if (!CanPause) return;
lastPauseActionTime = Time.Current; lastPauseActionTime = Time.Current;
});
OnPause?.Invoke();
pauseOverlay.Show();
});
}
public void Resume() public void Resume()
{ {
if (!IsPaused) return; if (!IsPaused) return;
IsPaused = false; IsPaused = false;
FramedClock.ProcessSourceClockFrames = true; IsResuming = false;
lastPauseActionTime = Time.Current; 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(); pauseOverlay.Hide();
AudioClock.Start();
IsResuming = false;
} }
private OsuGameBase game; private OsuGameBase game;
@ -122,6 +120,9 @@ namespace osu.Game.Screens.Play
if (!game.IsActive && CanPause) if (!game.IsActive && CanPause)
Pause(); Pause();
if (!IsPaused)
framedClock.ProcessFrame();
base.Update(); base.Update();
} }

View File

@ -56,9 +56,12 @@ namespace osu.Game.Screens.Play
public CursorContainer Cursor => RulesetContainer.Cursor; public CursorContainer Cursor => RulesetContainer.Cursor;
public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value; public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value;
private IAdjustableClock adjustableSourceClock; private IAdjustableClock sourceClock;
private FramedOffsetClock offsetClock;
private DecoupleableInterpolatingFramedClock decoupledClock; /// <summary>
/// The decoupled clock used for gameplay. Should be used for seeks and clock control.
/// </summary>
private DecoupleableInterpolatingFramedClock adjustableClock;
private PauseContainer pauseContainer; private PauseContainer pauseContainer;
@ -140,17 +143,18 @@ namespace osu.Game.Screens.Play
return; return;
} }
adjustableSourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
var firstObjectTime = RulesetContainer.Objects.First().StartTime; 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)) ? Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn))
: firstObjectTime); : 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<double>(OsuSetting.AudioOffset); userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
userAudioOffset.ValueChanged += v => offsetClock.Offset = v; userAudioOffset.ValueChanged += v => offsetClock.Offset = v;
@ -160,16 +164,8 @@ namespace osu.Game.Screens.Play
Children = new Drawable[] 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, OnRetry = Restart,
OnQuit = Exit, OnQuit = Exit,
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded,
@ -181,15 +177,23 @@ namespace osu.Game.Screens.Play
OnResume = () => hudOverlay.KeyCounter.IsCounting = true, OnResume = () => hudOverlay.KeyCounter.IsCounting = true,
Children = new Drawable[] Children = new Drawable[]
{ {
new Container storyboardContainer = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Clock = offsetClock, Alpha = 0,
Child = RulesetContainer,
}, },
new SkipButton(firstObjectTime) { AudioClock = decoupledClock }, RulesetContainer,
hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, decoupledClock, working, adjustableSourceClock) 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, Anchor = Anchor.Centre,
Origin = Anchor.Centre Origin = Anchor.Centre
}, },
@ -197,7 +201,7 @@ namespace osu.Game.Screens.Play
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Clock = decoupledClock, ProcessCustomClock = false,
Breaks = beatmap.Breaks Breaks = beatmap.Breaks
} }
} }
@ -234,11 +238,11 @@ namespace osu.Game.Screens.Play
private void applyRateFromMods() 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<IApplicableToClock>()) foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToClock>())
mod.ApplyToClock(adjustableSourceClock); mod.ApplyToClock(sourceClock);
} }
private void initializeStoryboard(bool asyncLoad) private void initializeStoryboard(bool asyncLoad)
@ -297,7 +301,7 @@ namespace osu.Game.Screens.Play
if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail)) if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
return false; return false;
decoupledClock.Stop(); adjustableClock.Stop();
HasFailed = true; HasFailed = true;
failOverlay.Retries = RestartCount; failOverlay.Retries = RestartCount;
@ -326,17 +330,19 @@ namespace osu.Game.Screens.Play
Task.Run(() => Task.Run(() =>
{ {
adjustableSourceClock.Reset(); sourceClock.Reset();
Schedule(() => Schedule(() =>
{ {
decoupledClock.ChangeSource(adjustableSourceClock); adjustableClock.ChangeSource(sourceClock);
applyRateFromMods(); applyRateFromMods();
this.Delay(750).Schedule(() => this.Delay(750).Schedule(() =>
{ {
if (!pauseContainer.IsPaused) if (!pauseContainer.IsPaused)
decoupledClock.Start(); {
adjustableClock.Start();
}
}); });
}); });
}); });
@ -363,9 +369,7 @@ namespace osu.Game.Screens.Play
} }
if (loadedSuccessfully) if (loadedSuccessfully)
{
pauseContainer?.Pause(); pauseContainer?.Pause();
}
return true; return true;
} }

View File

@ -24,7 +24,9 @@ namespace osu.Game.Screens.Play
public class SkipButton : OverlayContainer, IKeyBindingHandler<GlobalAction> public class SkipButton : OverlayContainer, IKeyBindingHandler<GlobalAction>
{ {
private readonly double startTime; private readonly double startTime;
public IAdjustableClock AudioClock;
public IAdjustableClock AdjustableClock;
public IFrameBasedClock FramedClock;
private Button button; private Button button;
private Box remainingTimeBox; private Box remainingTimeBox;
@ -60,8 +62,11 @@ namespace osu.Game.Screens.Play
{ {
var baseClock = Clock; var baseClock = Clock;
if (AudioClock != null) if (FramedClock != null)
Clock = new FramedClock(AudioClock) { ProcessSourceClockFrames = false }; {
Clock = FramedClock;
ProcessCustomClock = false;
}
Children = new Drawable[] Children = new Drawable[]
{ {
@ -109,7 +114,7 @@ namespace osu.Game.Screens.Play
using (BeginAbsoluteSequence(beginFadeTime)) using (BeginAbsoluteSequence(beginFadeTime))
this.FadeOut(fade_time); 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; displayTime = Time.Current;