1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-21 04:03:22 +08:00

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.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
@ -12,59 +10,93 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Screens.Play.HUD.KPSCounter
{
public class KeysPerSecondCalculator : IDisposable
public class KeysPerSecondCalculator
{
private static KeysPerSecondCalculator instance;
public static void AddInput()
{
instance?.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;
}
onNewInput?.Invoke();
}
private readonly List<double> timestamps;
private readonly GameplayClock gameplayClock;
private readonly DrawableRuleset drawableRuleset;
private GameplayClock? gameplayClock;
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()
// 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
public DrawableRuleset? DrawableRuleset
{
get => drawableRuleset;
set
{
onResetRequested?.Invoke();
if (value != null)
{
drawableRuleset = value;
baseRate = (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value
?? 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;
public bool Ready => workingClock != null && gameplayClock != null;
public int Value => timestamps.Count(isTimestampWithinSpan);
private KeysPerSecondCalculator(GameplayClock gameplayClock, DrawableRuleset drawableRuleset)
public KeysPerSecondCalculator()
{
instance = this;
timestamps = new List<double>();
this.gameplayClock = gameplayClock ?? throw new ArgumentNullException(nameof(gameplayClock));
this.drawableRuleset = drawableRuleset;
onNewInput += addTimestamp;
onResetRequested += cleanUp;
}
private void cleanUp()
{
timestamps.Clear();
maxTime = double.NegativeInfinity;
}
private void addTimestamp()
{
if (Ready && workingClock.CurrentTime >= maxTime && gameplayClock.TrueGameplayRate > 0)
if (workingClock == null) return;
if (workingClock.CurrentTime >= maxTime)
{
timestamps.Add(workingClock.CurrentTime);
maxTime = workingClock.CurrentTime;
@ -73,19 +105,11 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter
private bool isTimestampWithinSpan(double timestamp)
{
if (!Ready)
return false;
if (workingClock == null) return false;
double span = 1000 * rate;
double relativeTime = workingClock.CurrentTime - timestamp;
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.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
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 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)]
private DrawableRuleset drawableRuleset { get; set; }
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)
private DrawableRuleset? drawableRuleset
{
MaxValue = 1000,
MinValue = 0
};
get => calculator.DrawableRuleset;
set => calculator.DrawableRuleset = value;
}
protected override double RollingDuration => SmoothingTime.Value;
protected override double RollingDuration => 350;
public bool UsesFixedAnchor { get; set; }
@ -48,9 +48,8 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, GameplayClock clock, DrawableRuleset ruleset)
private void load(OsuColour colours)
{
gameplayClock = clock;
Colour = colours.BlueLighter;
valid.BindValueChanged(e =>
DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint));
@ -61,7 +60,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter
base.Update();
valid.Value = calculator.Ready;
Current.Value = calculator.Value;
Current.Value = calculator.Ready ? calculator.Value : 0;
}
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.Scoring;
using osu.Game.Scoring.Legacy;
using osu.Game.Screens.Play.HUD.KPSCounter;
using osu.Game.Screens.Ranking;
using osu.Game.Skinning;
using osu.Game.Users;
@ -1046,8 +1045,6 @@ namespace osu.Game.Screens.Play
fadeOut();
KeysPerSecondCalculator.GetInstance()?.Dispose();
return base.OnExiting(e);
}