1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 06:17:23 +08:00

Use a more explicit flow to set and reset GameplayClockContainer start time

This commit is contained in:
Dean Herbert 2022-03-17 20:54:42 +09:00
parent e3a8bb2d1c
commit a4a0241800
4 changed files with 48 additions and 33 deletions

View File

@ -41,6 +41,15 @@ namespace osu.Game.Screens.Play
/// </summary>
public event Action OnSeek;
/// <summary>
/// The time from which gameplay should start. Will be seeked to on calling <see cref="Reset"/>.
/// </summary>
/// <remarks>
/// If not set, a value of zero will be used.
/// Importantly, the value will be inferred from the current ruleset in <see cref="MasterGameplayClockContainer"/> unless specified.
/// </remarks>
protected double? GameplayStartTime { get; private set; }
/// <summary>
/// Creates a new <see cref="GameplayClockContainer"/>.
/// </summary>
@ -106,15 +115,20 @@ namespace osu.Game.Screens.Play
/// <summary>
/// Resets this <see cref="GameplayClockContainer"/> and the source to an initial state ready for gameplay.
/// </summary>
public virtual void Reset()
/// <param name="startClock">Whether to start the clock immediately.</param>
/// <param name="gameplayStartTime">A time to use for future <see cref="Reset"/> calls as the definite start of gameplay.</param>
public void Reset(bool startClock = false, double? gameplayStartTime = null)
{
if (gameplayStartTime != null)
GameplayStartTime = gameplayStartTime;
ensureSourceClockSet();
Seek(0);
Seek(GameplayStartTime ?? 0);
// Manually stop the source in order to not affect the IsPaused state.
AdjustableSource.Stop();
if (!IsPaused.Value)
if (!IsPaused.Value && startClock)
Start();
}

View File

@ -58,7 +58,6 @@ namespace osu.Game.Screens.Play
private HardwareCorrectionOffsetClock platformOffsetClock;
private MasterGameplayClock masterGameplayClock;
private Bindable<double> userAudioOffset;
private double startOffset;
private IDisposable beatmapOffsetSubscription;
@ -90,26 +89,33 @@ namespace osu.Game.Screens.Play
settings => settings.Offset,
val => userBeatmapOffsetClock.Offset = val);
// sane default provided by ruleset.
startOffset = gameplayStartTime;
if (!startAtGameplayStart)
if (GameplayStartTime == null)
{
startOffset = Math.Min(0, startOffset);
// sane default provided by ruleset.
double offset = gameplayStartTime;
// if a storyboard is present, it may dictate the appropriate start time by having events in negative time space.
// this is commonly used to display an intro before the audio track start.
double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime;
if (firstStoryboardEvent != null)
startOffset = Math.Min(startOffset, firstStoryboardEvent.Value);
if (!startAtGameplayStart)
{
offset = Math.Min(0, offset);
// some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available.
// this is not available as an option in the live editor but can still be applied via .osu editing.
if (beatmap.BeatmapInfo.AudioLeadIn > 0)
startOffset = Math.Min(startOffset, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn);
// if a storyboard is present, it may dictate the appropriate start time by having events in negative time space.
// this is commonly used to display an intro before the audio track start.
double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime;
if (firstStoryboardEvent != null)
offset = Math.Min(offset, firstStoryboardEvent.Value);
// some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available.
// this is not available as an option in the live editor but can still be applied via .osu editing.
if (beatmap.BeatmapInfo.AudioLeadIn > 0)
offset = Math.Min(offset, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn);
}
// Reset may have been called externally before LoadComplete.
// If it was and the clock is in a playing state, we want to ensure that it isn't stopped here.
bool isStarted = !IsPaused.Value;
Reset(startClock: isStarted, gameplayStartTime: offset);
}
Seek(startOffset);
}
protected override void OnIsPausedChanged(ValueChangedEvent<bool> isPaused)
@ -164,12 +170,6 @@ namespace osu.Game.Screens.Play
Seek(skipTarget);
}
public override void Reset()
{
base.Reset();
Seek(startOffset);
}
protected override GameplayClock CreateGameplayClock(IFrameBasedClock source)
{
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
@ -231,6 +231,7 @@ namespace osu.Game.Screens.Play
}
private class HardwareCorrectionOffsetClock : FramedOffsetClock
{
private readonly BindableDouble pauseRateAdjust;
@ -276,9 +277,9 @@ namespace osu.Game.Screens.Play
}
private class MasterGameplayClock : GameplayClock
{
public readonly List<Bindable<double>> MutableNonGameplayAdjustments = new List<Bindable<double>>();
public override IEnumerable<Bindable<double>> NonGameplayAdjustments => MutableNonGameplayAdjustments;
public MasterGameplayClock(FramedOffsetClock underlyingClock)

View File

@ -607,13 +607,13 @@ namespace osu.Game.Screens.Play
private ScheduledDelegate frameStablePlaybackResetDelegate;
/// <summary>
/// Seeks to a specific time in gameplay, bypassing frame stability.
/// Specify and seek to a custom start time from which gameplay should be observed.
/// </summary>
/// <remarks>
/// Intermediate hitobject judgements may not be applied or reverted correctly during this seek.
/// This performance a non-frame-stable seek. Intermediate hitobject judgements may not be applied or reverted correctly during this seek.
/// </remarks>
/// <param name="time">The destination time to seek to.</param>
internal void NonFrameStableSeek(double time)
protected void SetGameplayStartTime(double time)
{
if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed)
frameStablePlaybackResetDelegate.RunTask();
@ -621,7 +621,7 @@ namespace osu.Game.Screens.Play
bool wasFrameStable = DrawableRuleset.FrameStablePlayback;
DrawableRuleset.FrameStablePlayback = false;
Seek(time);
GameplayClockContainer.Reset(gameplayStartTime: time);
// Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek.
frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable);
@ -981,7 +981,7 @@ namespace osu.Game.Screens.Play
if (GameplayClockContainer.GameplayClock.IsRunning)
throw new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running");
GameplayClockContainer.Reset();
GameplayClockContainer.Reset(true);
}
public override void OnSuspending(IScreen next)

View File

@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play
}
if (isFirstBundle && score.Replay.Frames.Count > 0)
NonFrameStableSeek(score.Replay.Frames[0].Time);
SetGameplayStartTime(score.Replay.Frames[0].Time);
}
protected override Score CreateScore(IBeatmap beatmap) => score;