mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 20:07:29 +08:00
Move KPS calculation to a standalone class
This commit is contained in:
parent
89855cc1d6
commit
42d1bdfc95
@ -0,0 +1,91 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD.KPSCounter;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneKeyPerSecondCounter : PlayerTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
protected override bool HasCustomSteps => false;
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
private GameplayClock gameplayClock;
|
||||
private DrawableRuleset drawableRuleset;
|
||||
|
||||
// private DependencyProvidingContainer dependencyContainer;
|
||||
private KeysPerSecondCounter counter;
|
||||
|
||||
[SetUpSteps]
|
||||
public new void SetUpSteps()
|
||||
{
|
||||
/*
|
||||
CreateTest(() => AddStep("Create components", () =>
|
||||
{
|
||||
Logger.Log($"{(Player != null ? Player.ToString() : "null")}", level: LogLevel.Debug);
|
||||
dependencyContainer = new DependencyProvidingContainer
|
||||
{
|
||||
RelativePositionAxes = Axes.Both,
|
||||
};
|
||||
}));
|
||||
*/
|
||||
}
|
||||
|
||||
private void createCounter()
|
||||
{
|
||||
AddStep("Create counter", () =>
|
||||
{
|
||||
/*
|
||||
if (!Contains(dependencyContainer))
|
||||
{
|
||||
Add(dependencyContainer);
|
||||
}
|
||||
|
||||
if (dependencyContainer.CachedDependencies.Length == 0)
|
||||
{
|
||||
dependencyContainer.CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
(typeof(GameplayClock), ,
|
||||
(typeof(DrawableRuleset),)
|
||||
};
|
||||
}
|
||||
Dependencies.Cache(gameplayClock = Player.GameplayClockContainer.GameplayClock));
|
||||
*/
|
||||
|
||||
Dependencies.Cache(gameplayClock = Player.GameplayClockContainer.GameplayClock);
|
||||
Dependencies.Cache(drawableRuleset = Player.DrawableRuleset);
|
||||
|
||||
Add(counter = new KeysPerSecondCounter
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Scale = new Vector2(5),
|
||||
Position = new Vector2(10, 100)
|
||||
}
|
||||
);
|
||||
});
|
||||
AddAssert("ensure counter added", () => Contains(counter));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInGameTimeConsistency()
|
||||
{
|
||||
createCounter();
|
||||
|
||||
AddUntilStep("Wait until first note", () => counter.Current.Value != 0);
|
||||
AddStep("Pause gameplay", () => gameplayClock.IsPaused.Value = true);
|
||||
AddAssert("KPS = 1", () => counter.Current.Value == 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.KPSCounter;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public void TestManualTrigger()
|
||||
{
|
||||
AddAssert("Counter = 0", () => counter.Current.Value == 0);
|
||||
AddRepeatStep("manual trigger", KeysPerSecondCounter.AddTimestamp, 20);
|
||||
AddRepeatStep("manual trigger", KeysPerSecondCalculator.AddInput, 20);
|
||||
AddAssert("Counter is not 0", () => counter.Current.Value > 0);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,91 @@
|
||||
// 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;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD.KPSCounter
|
||||
{
|
||||
public class KeysPerSecondCalculator : IDisposable
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<double> timestamps;
|
||||
private readonly GameplayClock gameplayClock;
|
||||
private readonly DrawableRuleset drawableRuleset;
|
||||
|
||||
private event Action onNewInput;
|
||||
|
||||
private IClock workingClock => (IClock)drawableRuleset.FrameStableClock ?? gameplayClock;
|
||||
|
||||
// Having the rate from mods is preffered 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
|
||||
?? 1;
|
||||
|
||||
private double maxTime = double.NegativeInfinity;
|
||||
|
||||
public bool Ready => workingClock != null && gameplayClock != null;
|
||||
public int Value => timestamps.Count(isTimestampWithinSpan);
|
||||
|
||||
private KeysPerSecondCalculator(GameplayClock gameplayClock, DrawableRuleset drawableRuleset)
|
||||
{
|
||||
instance = this;
|
||||
timestamps = new List<double>();
|
||||
this.gameplayClock = gameplayClock ?? throw new ArgumentNullException(nameof(gameplayClock));
|
||||
this.drawableRuleset = drawableRuleset;
|
||||
onNewInput += addTimestamp;
|
||||
}
|
||||
|
||||
private void addTimestamp()
|
||||
{
|
||||
if (workingClock != null && workingClock.CurrentTime >= maxTime)
|
||||
{
|
||||
timestamps.Add(workingClock.CurrentTime);
|
||||
maxTime = workingClock.CurrentTime;
|
||||
}
|
||||
}
|
||||
|
||||
private bool isTimestampWithinSpan(double timestamp)
|
||||
{
|
||||
if (!Ready)
|
||||
return false;
|
||||
|
||||
double span = 1000 * rate;
|
||||
double relativeTime = workingClock.CurrentTime - timestamp;
|
||||
return relativeTime >= 0 && relativeTime <= span;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
instance = null;
|
||||
}
|
||||
|
||||
~KeysPerSecondCalculator() => Dispose();
|
||||
}
|
||||
}
|
@ -3,17 +3,12 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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.Framework.Logging;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -22,28 +17,20 @@ using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
namespace osu.Game.Screens.Play.HUD.KPSCounter
|
||||
{
|
||||
public class KeysPerSecondCounter : RollingCounter<int>, ISkinnableDrawable
|
||||
{
|
||||
private static List<double> timestamps;
|
||||
private static double maxTime = double.NegativeInfinity;
|
||||
|
||||
private static event Action onNewInput;
|
||||
|
||||
private const int invalidation_timeout = 1000;
|
||||
private const float alpha_when_invalid = 0.3f;
|
||||
|
||||
private readonly Bindable<bool> valid = new Bindable<bool>();
|
||||
|
||||
private static GameplayClock gameplayClock;
|
||||
private static IClock referenceClock;
|
||||
|
||||
private static IClock clock => referenceClock ?? gameplayClock;
|
||||
private GameplayClock gameplayClock;
|
||||
|
||||
[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)
|
||||
{
|
||||
@ -51,66 +38,30 @@ namespace osu.Game.Screens.Play.HUD
|
||||
MinValue = 0
|
||||
};
|
||||
|
||||
public static void AddTimestamp()
|
||||
{
|
||||
Logger.Log($"Input timestamp attempt C: {clock.CurrentTime}ms | GC: {gameplayClock.CurrentTime} | RC: {referenceClock?.CurrentTime ?? -1} | Max: {maxTime})", level: LogLevel.Debug);
|
||||
|
||||
if (clock.CurrentTime >= maxTime)
|
||||
{
|
||||
Logger.Log("Input timestamp added.", level: LogLevel.Debug);
|
||||
timestamps?.Add(clock.CurrentTime);
|
||||
maxTime = timestamps?.Max() ?? clock.CurrentTime;
|
||||
}
|
||||
|
||||
onNewInput?.Invoke();
|
||||
}
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
timestamps?.Clear();
|
||||
maxTime = int.MinValue;
|
||||
}
|
||||
|
||||
protected override double RollingDuration => SmoothingTime.Value;
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
|
||||
public KeysPerSecondCounter()
|
||||
{
|
||||
timestamps ??= new List<double>();
|
||||
Current.Value = 0;
|
||||
onNewInput += updateCounter;
|
||||
Scheduler.AddOnce(updateCounter);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, GameplayClock clock)
|
||||
private void load(OsuColour colours, GameplayClock clock, DrawableRuleset ruleset)
|
||||
{
|
||||
gameplayClock = clock;
|
||||
Colour = colours.BlueLighter;
|
||||
valid.BindValueChanged(e =>
|
||||
DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint));
|
||||
referenceClock = drawableRuleset?.FrameStableClock;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updateCounter();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
updateCounter();
|
||||
}
|
||||
|
||||
private void updateCounter()
|
||||
{
|
||||
valid.Value = timestamps != null && MathHelper.ApproximatelyEquivalent(gameplayClock.CurrentTime, referenceClock.CurrentTime, 500);
|
||||
Current.Value = timestamps?.Count(timestamp => clock.CurrentTime - timestamp is >= 0 and <= invalidation_timeout) ?? 0;
|
||||
valid.Value = calculator.Ready;
|
||||
Current.Value = calculator.Value;
|
||||
}
|
||||
|
||||
protected override IHasText CreateText() => new TextComponent
|
@ -10,7 +10,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.KPSCounter;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public void Increment()
|
||||
{
|
||||
KeysPerSecondCounter.AddTimestamp();
|
||||
KeysPerSecondCalculator.AddInput();
|
||||
if (!IsCounting)
|
||||
return;
|
||||
|
||||
|
@ -11,7 +11,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -66,7 +65,6 @@ namespace osu.Game.Screens.Play
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
KeysPerSecondCounter.Reset();
|
||||
config.BindWith(OsuSetting.KeyOverlay, configVisibility);
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ 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;
|
||||
using osu.Game.Screens.Play.HUD.KPSCounter;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Users;
|
||||
@ -1046,7 +1046,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
fadeOut();
|
||||
|
||||
KeysPerSecondCounter.Reset();
|
||||
KeysPerSecondCalculator.GetInstance().Dispose();
|
||||
|
||||
return base.OnExiting(e);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user