From b2557a8d2d4ca1ecde0cc6b709085df912316ede Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 7 Aug 2022 00:53:00 +0200 Subject: [PATCH] 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 --- .../HUD/KPSCounter/KeysPerSecondCalculator.cs | 106 +++++++++++------- .../HUD/KPSCounter/KeysPerSecondCounter.cs | 33 +++--- osu.Game/Screens/Play/Player.cs | 3 - 3 files changed, 81 insertions(+), 61 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index a29f4c9706..3c0d585984 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . 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 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(); - 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(); } } diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs index 2fcca2ffce..ad7b6c8f5c 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs @@ -1,15 +1,12 @@ // Copyright (c) ppy Pty Ltd . 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 valid = new Bindable(); - 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 SmoothingTime { get; } = new BindableNumber(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 diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 70b1dc9a41..e3844088e2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -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); }