1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 22:27:25 +08:00

Change KPS Counter implementation base and add better replay integration

The counter implementaiton is now list based, and will not invalidate
previous hits by removing them but by testing if they are within the 1
second span, allowing better integration with replays and spectators.
This commit is contained in:
Ryuki 2022-07-31 01:29:57 +02:00
parent 2df24019fd
commit 89855cc1d6
No known key found for this signature in database
GPG Key ID: A353889EAEACBF49
3 changed files with 57 additions and 16 deletions

View File

@ -1,17 +1,24 @@
// 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 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.Framework.Logging;
using osu.Framework.Timing;
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;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
@ -19,37 +26,71 @@ namespace osu.Game.Screens.Play.HUD
{ {
public class KeysPerSecondCounter : RollingCounter<int>, ISkinnableDrawable public class KeysPerSecondCounter : RollingCounter<int>, ISkinnableDrawable
{ {
private static Queue<DateTime>? timestamps; private static List<double> timestamps;
private static double maxTime = double.NegativeInfinity;
private static event Action? onNewInput; private static event Action onNewInput;
private readonly TimeSpan refreshSpan = TimeSpan.FromSeconds(1);
private const int invalidation_timeout = 1000;
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 static GameplayClock gameplayClock;
private static IClock referenceClock;
private static IClock clock => referenceClock ?? gameplayClock;
[Resolved(canBeNull: true)]
private DrawableRuleset drawableRuleset { get; set; }
[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,
MinValue = 0
};
public static void AddTimestamp() public static void AddTimestamp()
{ {
timestamps?.Enqueue(DateTime.Now); 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(); onNewInput?.Invoke();
} }
protected override double RollingDuration => 250; public static void Reset()
{
timestamps?.Clear();
maxTime = int.MinValue;
}
protected override double RollingDuration => SmoothingTime.Value;
public bool UsesFixedAnchor { get; set; } public bool UsesFixedAnchor { get; set; }
public KeysPerSecondCounter() public KeysPerSecondCounter()
{ {
timestamps ??= new Queue<DateTime>(); timestamps ??= new List<double>();
Current.Value = 0; Current.Value = 0;
onNewInput += updateCounter; onNewInput += updateCounter;
Scheduler.AddOnce(updateCounter);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours, GameplayClock clock)
{ {
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));
referenceClock = drawableRuleset?.FrameStableClock;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -61,21 +102,15 @@ namespace osu.Game.Screens.Play.HUD
protected override void Update() protected override void Update()
{ {
if (timestamps != null) base.Update();
{
if (timestamps.TryPeek(out var earliest) && DateTime.Now - earliest >= refreshSpan)
timestamps.Dequeue();
}
updateCounter(); updateCounter();
base.Update();
} }
private void updateCounter() private void updateCounter()
{ {
valid.Value = timestamps != null; valid.Value = timestamps != null && MathHelper.ApproximatelyEquivalent(gameplayClock.CurrentTime, referenceClock.CurrentTime, 500);
Current.Value = timestamps?.Count ?? 0; Current.Value = timestamps?.Count(timestamp => clock.CurrentTime - timestamp is >= 0 and <= invalidation_timeout) ?? 0;
} }
protected override IHasText CreateText() => new TextComponent protected override IHasText CreateText() => new TextComponent

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Screens.Play.HUD;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -65,6 +66,7 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
KeysPerSecondCounter.Reset();
config.BindWith(OsuSetting.KeyOverlay, configVisibility); config.BindWith(OsuSetting.KeyOverlay, configVisibility);
} }

View File

@ -34,6 +34,7 @@ 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;
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;
@ -1044,6 +1045,9 @@ namespace osu.Game.Screens.Play
musicController.ResetTrackAdjustments(); musicController.ResetTrackAdjustments();
fadeOut(); fadeOut();
KeysPerSecondCounter.Reset();
return base.OnExiting(e); return base.OnExiting(e);
} }