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

Merge pull request #10371 from peppy/fix-gameplay-seek-sample-pausing

Fix seeking in replays not correctly pausing samples
This commit is contained in:
Dan Balasescu 2020-10-05 17:13:43 +09:00 committed by GitHub
commit 12c84df208
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 92 additions and 15 deletions

View File

@ -163,7 +163,6 @@ namespace osu.Game.Rulesets.Taiko.UI
target = centreHit; target = centreHit;
back = centre; back = centre;
if (gameplayClock?.IsSeeking != true)
drumSample.Centre?.Play(); drumSample.Centre?.Play();
} }
else if (action == RimAction) else if (action == RimAction)
@ -171,7 +170,6 @@ namespace osu.Game.Rulesets.Taiko.UI
target = rimHit; target = rimHit;
back = rim; back = rim;
if (gameplayClock?.IsSeeking != true)
drumSample.Rim?.Play(); drumSample.Rim?.Play();
} }

View File

@ -0,0 +1,61 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Audio;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneGameplaySamplePlayback : PlayerTestScene
{
[Test]
public void TestAllSamplesStopDuringSeek()
{
DrawableSlider slider = null;
DrawableSample[] samples = null;
ISamplePlaybackDisabler gameplayClock = null;
AddStep("get variables", () =>
{
gameplayClock = Player.ChildrenOfType<FrameStabilityContainer>().First().GameplayClock;
slider = Player.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
samples = slider.ChildrenOfType<DrawableSample>().ToArray();
});
AddUntilStep("wait for slider sliding then seek", () =>
{
if (!slider.Tracking.Value)
return false;
if (!samples.Any(s => s.Playing))
return false;
Player.ChildrenOfType<GameplayClockContainer>().First().Seek(40000);
return true;
});
AddAssert("sample playback disabled", () => gameplayClock.SamplePlaybackDisabled.Value);
// because we are in frame stable context, it's quite likely that not all samples are "played" at this point.
// the important thing is that at least one started, and that sample has since stopped.
AddAssert("no samples are playing", () => Player.ChildrenOfType<PausableSkinnableSound>().All(s => !s.IsPlaying));
AddAssert("sample playback still disabled", () => gameplayClock.SamplePlaybackDisabled.Value);
AddUntilStep("seek finished, sample playback enabled", () => !gameplayClock.SamplePlaybackDisabled.Value);
AddUntilStep("any sample is playing", () => Player.ChildrenOfType<PausableSkinnableSound>().Any(s => s.IsPlaying));
}
protected override bool Autoplay => true;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
}
}

View File

@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.UI
public GameplayClock GameplayClock => stabilityGameplayClock; public GameplayClock GameplayClock => stabilityGameplayClock;
[Cached(typeof(GameplayClock))] [Cached(typeof(GameplayClock))]
[Cached(typeof(ISamplePlaybackDisabler))]
private readonly StabilityGameplayClock stabilityGameplayClock; private readonly StabilityGameplayClock stabilityGameplayClock;
public FrameStabilityContainer(double gameplayStartTime = double.MinValue) public FrameStabilityContainer(double gameplayStartTime = double.MinValue)
@ -58,13 +59,16 @@ namespace osu.Game.Rulesets.UI
private int direction; private int direction;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(GameplayClock clock) private void load(GameplayClock clock, ISamplePlaybackDisabler sampleDisabler)
{ {
if (clock != null) if (clock != null)
{ {
parentGameplayClock = stabilityGameplayClock.ParentGameplayClock = clock; parentGameplayClock = stabilityGameplayClock.ParentGameplayClock = clock;
GameplayClock.IsPaused.BindTo(clock.IsPaused); GameplayClock.IsPaused.BindTo(clock.IsPaused);
} }
// this is a bit temporary. should really be done inside of GameplayClock (but requires large structural changes).
stabilityGameplayClock.ParentSampleDisabler = sampleDisabler;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -206,12 +210,16 @@ namespace osu.Game.Rulesets.UI
} }
private void setClock() private void setClock()
{
if (parentGameplayClock == null)
{ {
// in case a parent gameplay clock isn't available, just use the parent clock. // in case a parent gameplay clock isn't available, just use the parent clock.
parentGameplayClock ??= Clock; parentGameplayClock ??= Clock;
}
else
{
Clock = GameplayClock; Clock = GameplayClock;
ProcessCustomClock = false; }
} }
public ReplayInputHandler ReplayInputHandler { get; set; } public ReplayInputHandler ReplayInputHandler { get; set; }
@ -220,6 +228,8 @@ namespace osu.Game.Rulesets.UI
{ {
public GameplayClock ParentGameplayClock; public GameplayClock ParentGameplayClock;
public ISamplePlaybackDisabler ParentSampleDisabler;
public override IEnumerable<Bindable<double>> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty<Bindable<double>>(); public override IEnumerable<Bindable<double>> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty<Bindable<double>>();
public StabilityGameplayClock(FramedClock underlyingClock) public StabilityGameplayClock(FramedClock underlyingClock)
@ -227,7 +237,11 @@ namespace osu.Game.Rulesets.UI
{ {
} }
public override bool IsSeeking => ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200; protected override bool ShouldDisableSamplePlayback =>
// handle the case where playback is catching up to real-time.
base.ShouldDisableSamplePlayback
|| ParentSampleDisabler?.SamplePlaybackDisabled.Value == true
|| (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200);
} }
} }
} }

View File

@ -28,6 +28,8 @@ namespace osu.Game.Screens.Play
/// </summary> /// </summary>
public virtual IEnumerable<Bindable<double>> NonGameplayAdjustments => Enumerable.Empty<Bindable<double>>(); public virtual IEnumerable<Bindable<double>> NonGameplayAdjustments => Enumerable.Empty<Bindable<double>>();
private readonly Bindable<bool> samplePlaybackDisabled = new Bindable<bool>();
public GameplayClock(IFrameBasedClock underlyingClock) public GameplayClock(IFrameBasedClock underlyingClock)
{ {
this.underlyingClock = underlyingClock; this.underlyingClock = underlyingClock;
@ -62,13 +64,15 @@ namespace osu.Game.Screens.Play
public bool IsRunning => underlyingClock.IsRunning; public bool IsRunning => underlyingClock.IsRunning;
/// <summary> /// <summary>
/// Whether an ongoing seek operation is active. /// Whether nested samples supporting the <see cref="ISamplePlaybackDisabler"/> interface should be paused.
/// </summary> /// </summary>
public virtual bool IsSeeking => false; protected virtual bool ShouldDisableSamplePlayback => IsPaused.Value;
public void ProcessFrame() public void ProcessFrame()
{ {
// we do not want to process the underlying clock. // intentionally not updating the underlying clock (handled externally).
samplePlaybackDisabled.Value = ShouldDisableSamplePlayback;
} }
public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime;
@ -79,6 +83,6 @@ namespace osu.Game.Screens.Play
public IClock Source => underlyingClock; public IClock Source => underlyingClock;
public IBindable<bool> SamplePlaybackDisabled => IsPaused; IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled;
} }
} }