mirror of
https://github.com/ppy/osu
synced 2024-12-16 11:56:31 +00:00
Merge pull request #19776 from peppy/even-less-gameplay-clock
Remove `FrameStableClock` (and redirect usages to `FrameStabilityContainer`)
This commit is contained in:
commit
5a88f9c7ee
@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Timing;
|
||||
@ -30,7 +31,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
public List<Bindable<double>> MutableNonGameplayAdjustments { get; } = new List<Bindable<double>>();
|
||||
|
||||
public override IEnumerable<Bindable<double>> NonGameplayAdjustments => MutableNonGameplayAdjustments;
|
||||
public override IEnumerable<double> NonGameplayAdjustments => MutableNonGameplayAdjustments.Select(b => b.Value);
|
||||
|
||||
public TestGameplayClock(IFrameBasedClock underlyingClock)
|
||||
: base(underlyingClock)
|
||||
|
@ -137,13 +137,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time);
|
||||
|
||||
private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime == time);
|
||||
private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime, () => Is.EqualTo(time));
|
||||
|
||||
private void checkFrameCount(int frames) =>
|
||||
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames);
|
||||
AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames, () => Is.EqualTo(frames));
|
||||
|
||||
private void checkRate(double rate) =>
|
||||
AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate == rate);
|
||||
AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate, () => Is.EqualTo(rate));
|
||||
|
||||
public class ClockConsumingChild : CompositeDrawable
|
||||
{
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@ -21,22 +19,22 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestAllSamplesStopDuringSeek()
|
||||
{
|
||||
DrawableSlider slider = null;
|
||||
PoolableSkinnableSample[] samples = null;
|
||||
ISamplePlaybackDisabler sampleDisabler = null;
|
||||
DrawableSlider? slider = null;
|
||||
PoolableSkinnableSample[] samples = null!;
|
||||
ISamplePlaybackDisabler sampleDisabler = null!;
|
||||
|
||||
AddUntilStep("get variables", () =>
|
||||
{
|
||||
sampleDisabler = Player;
|
||||
slider = Player.ChildrenOfType<DrawableSlider>().MinBy(s => s.HitObject.StartTime);
|
||||
samples = slider?.ChildrenOfType<PoolableSkinnableSample>().ToArray();
|
||||
samples = slider.ChildrenOfType<PoolableSkinnableSample>().ToArray();
|
||||
|
||||
return slider != null;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for slider sliding then seek", () =>
|
||||
{
|
||||
if (!slider.Tracking.Value)
|
||||
if (slider?.Tracking.Value != true)
|
||||
return false;
|
||||
|
||||
if (!samples.Any(s => s.Playing))
|
||||
|
@ -363,7 +363,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private Player player => Stack.CurrentScreen as Player;
|
||||
|
||||
private double currentFrameStableTime
|
||||
=> player.ChildrenOfType<FrameStabilityContainer>().First().FrameStableClock.CurrentTime;
|
||||
=> player.ChildrenOfType<FrameStabilityContainer>().First().CurrentTime;
|
||||
|
||||
private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true);
|
||||
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
public override IFrameStableClock FrameStableClock => frameStabilityContainer.FrameStableClock;
|
||||
public override IFrameStableClock FrameStableClock => frameStabilityContainer;
|
||||
|
||||
private bool frameStablePlayback = true;
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@ -12,6 +10,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
@ -21,7 +20,9 @@ namespace osu.Game.Rulesets.UI
|
||||
/// A container which consumes a parent gameplay clock and standardises frame counts for children.
|
||||
/// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks.
|
||||
/// </summary>
|
||||
public class FrameStabilityContainer : Container, IHasReplayHandler
|
||||
[Cached(typeof(IGameplayClock))]
|
||||
[Cached(typeof(IFrameStableClock))]
|
||||
public class FrameStabilityContainer : Container, IHasReplayHandler, IFrameStableClock, IGameplayClock
|
||||
{
|
||||
private readonly double gameplayStartTime;
|
||||
|
||||
@ -35,16 +36,17 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </summary>
|
||||
internal bool FrameStablePlayback = true;
|
||||
|
||||
public IFrameStableClock FrameStableClock => frameStableClock;
|
||||
public readonly Bindable<bool> IsCatchingUp = new Bindable<bool>();
|
||||
|
||||
[Cached(typeof(IGameplayClock))]
|
||||
private readonly FrameStabilityClock frameStableClock;
|
||||
public readonly Bindable<bool> WaitingOnFrames = new Bindable<bool>();
|
||||
|
||||
public IBindable<bool> IsPaused { get; } = new BindableBool();
|
||||
|
||||
public FrameStabilityContainer(double gameplayStartTime = double.MinValue)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
frameStableClock = new FrameStabilityClock(framedClock = new FramedClock(manualClock = new ManualClock()));
|
||||
framedClock = new FramedClock(manualClock = new ManualClock());
|
||||
|
||||
this.gameplayStartTime = gameplayStartTime;
|
||||
}
|
||||
@ -53,7 +55,9 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
private readonly FramedClock framedClock;
|
||||
|
||||
private IFrameBasedClock parentGameplayClock;
|
||||
private IGameplayClock? parentGameplayClock;
|
||||
|
||||
private IClock referenceClock = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The current direction of playback to be exposed to frame stable children.
|
||||
@ -63,20 +67,17 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </remarks>
|
||||
private int direction = 1;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(IGameplayClock clock)
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IGameplayClock? gameplayClock)
|
||||
{
|
||||
if (clock != null)
|
||||
if (gameplayClock != null)
|
||||
{
|
||||
parentGameplayClock = frameStableClock.ParentGameplayClock = clock;
|
||||
((IBindable<bool>)frameStableClock.IsPaused).BindTo(clock.IsPaused);
|
||||
parentGameplayClock = gameplayClock;
|
||||
IsPaused.BindTo(parentGameplayClock.IsPaused);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
setClock();
|
||||
referenceClock = gameplayClock ?? Clock;
|
||||
Clock = this;
|
||||
}
|
||||
|
||||
private PlaybackState state;
|
||||
@ -111,12 +112,12 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
private void updateClock()
|
||||
{
|
||||
if (frameStableClock.WaitingOnFrames.Value)
|
||||
if (WaitingOnFrames.Value)
|
||||
{
|
||||
// if waiting on frames, run one update loop to determine if frames have arrived.
|
||||
state = PlaybackState.Valid;
|
||||
}
|
||||
else if (frameStableClock.IsPaused.Value)
|
||||
else if (IsPaused.Value)
|
||||
{
|
||||
// time should not advance while paused, nor should anything run.
|
||||
state = PlaybackState.NotValid;
|
||||
@ -127,12 +128,7 @@ namespace osu.Game.Rulesets.UI
|
||||
state = PlaybackState.Valid;
|
||||
}
|
||||
|
||||
if (parentGameplayClock == null)
|
||||
setClock(); // LoadComplete may not be run yet, but we still want the clock.
|
||||
|
||||
Debug.Assert(parentGameplayClock != null);
|
||||
|
||||
double proposedTime = parentGameplayClock.CurrentTime;
|
||||
double proposedTime = referenceClock.CurrentTime;
|
||||
|
||||
if (FrameStablePlayback)
|
||||
// if we require frame stability, the proposed time will be adjusted to move at most one known
|
||||
@ -152,14 +148,14 @@ namespace osu.Game.Rulesets.UI
|
||||
if (state == PlaybackState.Valid && proposedTime != manualClock.CurrentTime)
|
||||
direction = proposedTime >= manualClock.CurrentTime ? 1 : -1;
|
||||
|
||||
double timeBehind = Math.Abs(proposedTime - parentGameplayClock.CurrentTime);
|
||||
double timeBehind = Math.Abs(proposedTime - referenceClock.CurrentTime);
|
||||
|
||||
frameStableClock.IsCatchingUp.Value = timeBehind > 200;
|
||||
frameStableClock.WaitingOnFrames.Value = state == PlaybackState.NotValid;
|
||||
IsCatchingUp.Value = timeBehind > 200;
|
||||
WaitingOnFrames.Value = state == PlaybackState.NotValid;
|
||||
|
||||
manualClock.CurrentTime = proposedTime;
|
||||
manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction;
|
||||
manualClock.IsRunning = parentGameplayClock.IsRunning;
|
||||
manualClock.Rate = Math.Abs(referenceClock.Rate) * direction;
|
||||
manualClock.IsRunning = referenceClock.IsRunning;
|
||||
|
||||
// determine whether catch-up is required.
|
||||
if (state == PlaybackState.Valid && timeBehind > 0)
|
||||
@ -177,6 +173,8 @@ namespace osu.Game.Rulesets.UI
|
||||
/// <returns>Whether playback is still valid.</returns>
|
||||
private bool updateReplay(ref double proposedTime)
|
||||
{
|
||||
Debug.Assert(ReplayInputHandler != null);
|
||||
|
||||
double? newTime;
|
||||
|
||||
if (FrameStablePlayback)
|
||||
@ -236,20 +234,51 @@ namespace osu.Game.Rulesets.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void setClock()
|
||||
public ReplayInputHandler? ReplayInputHandler { get; set; }
|
||||
|
||||
#region Delegation of IFrameStableClock
|
||||
|
||||
public double CurrentTime => framedClock.CurrentTime;
|
||||
|
||||
public double Rate => framedClock.Rate;
|
||||
|
||||
public bool IsRunning => framedClock.IsRunning;
|
||||
|
||||
public void ProcessFrame() { }
|
||||
|
||||
public double ElapsedFrameTime => framedClock.ElapsedFrameTime;
|
||||
|
||||
public double FramesPerSecond => framedClock.FramesPerSecond;
|
||||
|
||||
public FrameTimeInfo TimeInfo => framedClock.TimeInfo;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delegation of IGameplayClock
|
||||
|
||||
public double TrueGameplayRate
|
||||
{
|
||||
if (parentGameplayClock == null)
|
||||
get
|
||||
{
|
||||
// in case a parent gameplay clock isn't available, just use the parent clock.
|
||||
parentGameplayClock ??= Clock;
|
||||
}
|
||||
else
|
||||
{
|
||||
Clock = frameStableClock;
|
||||
double baseRate = Rate;
|
||||
|
||||
foreach (double adjustment in NonGameplayAdjustments)
|
||||
{
|
||||
if (Precision.AlmostEquals(adjustment, 0))
|
||||
return 0;
|
||||
|
||||
baseRate /= adjustment;
|
||||
}
|
||||
|
||||
return baseRate;
|
||||
}
|
||||
}
|
||||
|
||||
public ReplayInputHandler ReplayInputHandler { get; set; }
|
||||
public double? StartTime => parentGameplayClock?.StartTime;
|
||||
|
||||
public IEnumerable<double> NonGameplayAdjustments => parentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty<double>();
|
||||
|
||||
#endregion
|
||||
|
||||
private enum PlaybackState
|
||||
{
|
||||
@ -270,24 +299,7 @@ namespace osu.Game.Rulesets.UI
|
||||
Valid
|
||||
}
|
||||
|
||||
private class FrameStabilityClock : GameplayClock, IFrameStableClock
|
||||
{
|
||||
public IGameplayClock ParentGameplayClock;
|
||||
|
||||
public readonly Bindable<bool> IsCatchingUp = new Bindable<bool>();
|
||||
|
||||
public readonly Bindable<bool> WaitingOnFrames = new Bindable<bool>();
|
||||
|
||||
public override IEnumerable<Bindable<double>> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty<Bindable<double>>();
|
||||
|
||||
public FrameStabilityClock(FramedClock underlyingClock)
|
||||
: base(underlyingClock)
|
||||
{
|
||||
}
|
||||
|
||||
IBindable<bool> IFrameStableClock.IsCatchingUp => IsCatchingUp;
|
||||
|
||||
IBindable<bool> IFrameStableClock.WaitingOnFrames => WaitingOnFrames;
|
||||
}
|
||||
IBindable<bool> IFrameStableClock.IsCatchingUp => IsCatchingUp;
|
||||
IBindable<bool> IFrameStableClock.WaitingOnFrames => WaitingOnFrames;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Timing;
|
||||
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
IBindable<bool> IGameplayClock.IsPaused => IsPaused;
|
||||
|
||||
public virtual IEnumerable<Bindable<double>> NonGameplayAdjustments => Enumerable.Empty<Bindable<double>>();
|
||||
public virtual IEnumerable<double> NonGameplayAdjustments => Enumerable.Empty<double>();
|
||||
|
||||
public GameplayClock(IFrameBasedClock underlyingClock)
|
||||
{
|
||||
@ -46,12 +46,12 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
double baseRate = Rate;
|
||||
|
||||
foreach (var adjustment in NonGameplayAdjustments)
|
||||
foreach (double adjustment in NonGameplayAdjustments)
|
||||
{
|
||||
if (Precision.AlmostEquals(adjustment.Value, 0))
|
||||
if (Precision.AlmostEquals(adjustment, 0))
|
||||
return 0;
|
||||
|
||||
baseRate /= adjustment.Value;
|
||||
baseRate /= adjustment;
|
||||
}
|
||||
|
||||
return baseRate;
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Bindable<double>> NonGameplayAdjustments => GameplayClock.NonGameplayAdjustments;
|
||||
public IEnumerable<double> NonGameplayAdjustments => GameplayClock.NonGameplayAdjustments;
|
||||
|
||||
/// <summary>
|
||||
/// The final clock which is exposed to gameplay components.
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play
|
||||
/// <summary>
|
||||
/// All adjustments applied to this clock which don't come from gameplay or mods.
|
||||
/// </summary>
|
||||
IEnumerable<Bindable<double>> NonGameplayAdjustments { get; }
|
||||
IEnumerable<double> NonGameplayAdjustments { get; }
|
||||
|
||||
IBindable<bool> IsPaused { get; }
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ 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 override IEnumerable<double> NonGameplayAdjustments => MutableNonGameplayAdjustments.Select(b => b.Value);
|
||||
|
||||
public MasterGameplayClock(FramedOffsetClock underlyingClock)
|
||||
: base(underlyingClock)
|
||||
|
Loading…
Reference in New Issue
Block a user