Refactor KPS

- Remove '#nullable disable' in KeysPerSecondCalculator and
  KeysPerSecondCounter
- Remove KeysPerSecondCalculator IDisposable implementation
- Make KeysPerSecondCalculator static instance initialized once by
  KeysPerSecondCounters
- Auto transfer dependencies from KeysPerSecondCounter to
  KeysPerSecondCalculator using Resolved properties
- Add internal reset logic to KeysPerSecondCalculator and make it
  independent from Player
- Use GameplayClock.TrueGameplayRate to get real-time rate. If 0 then it
  defaults to the last non 0 rate if no such mod is enabled
This commit is contained in:
Ryuki 2022-08-07 00:53:00 +02:00
parent 0886137e39
commit b2557a8d2d
No known key found for this signature in database
GPG Key ID: A353889EAEACBF49
3 changed files with 81 additions and 61 deletions

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -12,59 +10,93 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Screens.Play.HUD.KPSCounter namespace osu.Game.Screens.Play.HUD.KPSCounter
{ {
public class KeysPerSecondCalculator : IDisposable public class KeysPerSecondCalculator
{ {
private static KeysPerSecondCalculator instance;
public static void AddInput() public static void AddInput()
{ {
instance?.onNewInput?.Invoke(); onNewInput?.Invoke();
}
public static KeysPerSecondCalculator GetInstance(GameplayClock gameplayClock = null, DrawableRuleset drawableRuleset = null)
{
if (instance != null) return instance;
try
{
return new KeysPerSecondCalculator(gameplayClock, drawableRuleset);
}
catch (ArgumentNullException)
{
return null;
}
} }
private readonly List<double> timestamps; private readonly List<double> timestamps;
private readonly GameplayClock gameplayClock; private GameplayClock? gameplayClock;
private readonly DrawableRuleset drawableRuleset; private DrawableRuleset? drawableRuleset;
private event Action onNewInput; public GameplayClock? GameplayClock
{
get => gameplayClock;
set
{
onResetRequested?.Invoke();
private IClock workingClock => (IClock)drawableRuleset?.FrameStableClock ?? gameplayClock; if (value != null)
{
gameplayClock = value;
}
}
}
// Having the rate from mods is preferred to using GameplayClock.TrueGameplayRate() public DrawableRuleset? DrawableRuleset
// as it returns 0 when paused in replays, not useful for players who want to "analyze" a replay. {
private double rate => (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value get => drawableRuleset;
set
{
onResetRequested?.Invoke();
if (value != null)
{
drawableRuleset = value;
baseRate = (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value
?? 1; ?? 1;
}
}
}
private static event Action? onNewInput;
private static event Action? onResetRequested;
private IClock? workingClock => drawableRuleset?.FrameStableClock;
private double baseRate;
private double rate
{
get
{
if (gameplayClock != null)
{
if (gameplayClock.TrueGameplayRate > 0)
{
baseRate = gameplayClock.TrueGameplayRate;
}
}
return baseRate;
}
}
private double maxTime = double.NegativeInfinity; private double maxTime = double.NegativeInfinity;
public bool Ready => workingClock != null && gameplayClock != null; public bool Ready => workingClock != null && gameplayClock != null;
public int Value => timestamps.Count(isTimestampWithinSpan); public int Value => timestamps.Count(isTimestampWithinSpan);
private KeysPerSecondCalculator(GameplayClock gameplayClock, DrawableRuleset drawableRuleset) public KeysPerSecondCalculator()
{ {
instance = this;
timestamps = new List<double>(); timestamps = new List<double>();
this.gameplayClock = gameplayClock ?? throw new ArgumentNullException(nameof(gameplayClock));
this.drawableRuleset = drawableRuleset;
onNewInput += addTimestamp; onNewInput += addTimestamp;
onResetRequested += cleanUp;
}
private void cleanUp()
{
timestamps.Clear();
maxTime = double.NegativeInfinity;
} }
private void addTimestamp() private void addTimestamp()
{ {
if (Ready && workingClock.CurrentTime >= maxTime && gameplayClock.TrueGameplayRate > 0) if (workingClock == null) return;
if (workingClock.CurrentTime >= maxTime)
{ {
timestamps.Add(workingClock.CurrentTime); timestamps.Add(workingClock.CurrentTime);
maxTime = workingClock.CurrentTime; maxTime = workingClock.CurrentTime;
@ -73,19 +105,11 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter
private bool isTimestampWithinSpan(double timestamp) private bool isTimestampWithinSpan(double timestamp)
{ {
if (!Ready) if (workingClock == null) return false;
return false;
double span = 1000 * rate; double span = 1000 * rate;
double relativeTime = workingClock.CurrentTime - timestamp; double relativeTime = workingClock.CurrentTime - timestamp;
return relativeTime >= 0 && relativeTime <= span; return relativeTime >= 0 && relativeTime <= span;
} }
public void Dispose()
{
instance = null;
}
~KeysPerSecondCalculator() => Dispose();
} }
} }

View File

@ -1,15 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
@ -24,21 +21,24 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter
private const float alpha_when_invalid = 0.3f; private const float alpha_when_invalid = 0.3f;
private readonly Bindable<bool> valid = new Bindable<bool>(); private readonly Bindable<bool> valid = new Bindable<bool>();
private GameplayClock gameplayClock;
private static readonly KeysPerSecondCalculator calculator = new KeysPerSecondCalculator();
[Resolved]
private GameplayClock? gameplayClock
{
get => calculator.GameplayClock;
set => calculator.GameplayClock = value;
}
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private DrawableRuleset drawableRuleset { get; set; } private DrawableRuleset? drawableRuleset
private KeysPerSecondCalculator calculator => KeysPerSecondCalculator.GetInstance(gameplayClock, drawableRuleset);
[SettingSource("Smoothing time", "How smooth the counter should change\nThe more it is smooth, the less it's accurate.")]
public BindableNumber<double> SmoothingTime { get; } = new BindableNumber<double>(350)
{ {
MaxValue = 1000, get => calculator.DrawableRuleset;
MinValue = 0 set => calculator.DrawableRuleset = value;
}; }
protected override double RollingDuration => SmoothingTime.Value; protected override double RollingDuration => 350;
public bool UsesFixedAnchor { get; set; } public bool UsesFixedAnchor { get; set; }
@ -48,9 +48,8 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, GameplayClock clock, DrawableRuleset ruleset) private void load(OsuColour colours)
{ {
gameplayClock = clock;
Colour = colours.BlueLighter; Colour = colours.BlueLighter;
valid.BindValueChanged(e => valid.BindValueChanged(e =>
DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint));
@ -61,7 +60,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter
base.Update(); base.Update();
valid.Value = calculator.Ready; valid.Value = calculator.Ready;
Current.Value = calculator.Value; Current.Value = calculator.Ready ? calculator.Value : 0;
} }
protected override IHasText CreateText() => new TextComponent protected override IHasText CreateText() => new TextComponent

View File

@ -34,7 +34,6 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Scoring.Legacy; using osu.Game.Scoring.Legacy;
using osu.Game.Screens.Play.HUD.KPSCounter;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Users; using osu.Game.Users;
@ -1046,8 +1045,6 @@ namespace osu.Game.Screens.Play
fadeOut(); fadeOut();
KeysPerSecondCalculator.GetInstance()?.Dispose();
return base.OnExiting(e); return base.OnExiting(e);
} }