1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 22:22:55 +08:00

Tidy up clock logic using DI and a GameplayClock

This commit is contained in:
Dean Herbert 2019-03-05 13:26:54 +09:00
parent 7c54e67b6e
commit 01f1018d02
11 changed files with 127 additions and 83 deletions

View File

@ -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

View File

@ -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<WorkingBeatmap> beatmap)
[BackgroundDependencyLoader(true)]
private void load(IBindable<WorkingBeatmap> beatmap, GameplayClock clock)
{
this.beatmap = beatmap.Value;
if (clock != null) Clock = clock;
}
/// <summary>

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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;
/// <summary>
/// The final clock which is exposed to underlying components.
/// </summary>
[Cached]
private readonly GameplayClock gameplayClock;
/// <summary>
/// Creates a new <see cref="PauseContainer"/>.
/// </summary>
/// <param name="framedClock">The gameplay clock. This is the clock that will process frames.</param>
/// <param name="decoupledClock">The seekable clock. This is the clock that will be paused and resumed.</param>
public PauseContainer(FramedClock framedClock, DecoupleableInterpolatingFramedClock decoupledClock)
/// <param name="offsetClock">The gameplay clock. This is the clock that will process frames. Includes user/system offsets.</param>
/// <param name="adjustableClock">The seekable clock. This is the clock that will be paused and resumed. Should not be processed (it is processed automatically by <see cref="offsetClock"/>).</param>
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
}
}
}
/// <summary>
/// A clock which is used for gameplay elements that need to follow audio time 1:1.
/// Exposed via DI by <see cref="PauseContainer"/>.
/// <remarks>
/// THe main purpose of this clock is to stop components using it from accidentally processing the main
/// <see cref="IFrameBasedClock"/>, as this should only be done once to ensure accuracy.
/// </remarks>
/// </summary>
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;
}
}

View File

@ -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)
},
}
},

View File

@ -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<double> 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;

View File

@ -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<double> OnSeek;
public Action<double> 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);
}
}
}

View File

@ -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;
}