Fix HUD unstable rate counter not including judgements before load complete

Also unifies UR calculation logic with the results screen statistic item
to reduce duplication.
This commit is contained in:
Bartłomiej Dach 2021-11-11 13:09:48 +01:00
parent 40cffd1682
commit 69809390d3
No known key found for this signature in database
GPG Key ID: BCECCD4FA41F6497
4 changed files with 52 additions and 54 deletions

View File

@ -22,7 +22,8 @@ namespace osu.Game.Tests.NonVisual.Ranking
var unstableRate = new UnstableRate(events);
Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value, 10 * Math.Sqrt(10)));
Assert.IsNotNull(unstableRate.Value);
Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value.Value, 10 * Math.Sqrt(10)));
}
[Test]

View File

@ -0,0 +1,37 @@
// 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.
using System;
using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Rulesets.Scoring
{
public static class HitEventExtensions
{
/// <summary>
/// Calculates the "unstable rate" for a sequence of <see cref="HitEvent"/>s.
/// </summary>
/// <returns>
/// A non-null <see langword="double"/> value if unstable rate could be calculated,
/// and <see langword="null"/> if unstable rate cannot be calculated due to <paramref name="hitEvents"/> being empty.
/// </returns>
public static double? CalculateUnstableRate(this IEnumerable<HitEvent> hitEvents)
{
double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).ToArray();
return 10 * standardDeviation(timeOffsets);
}
private static bool affectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit();
private static double? standardDeviation(double[] timeOffsets)
{
if (timeOffsets.Length == 0)
return null;
double mean = timeOffsets.Average();
double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum();
return Math.Sqrt(squares / timeOffsets.Length);
}
}
}

View File

@ -1,9 +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.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -29,8 +26,6 @@ namespace osu.Game.Screens.Play.HUD
private const float alpha_when_invalid = 0.3f;
private readonly Bindable<bool> valid = new Bindable<bool>();
private readonly List<double> hitOffsets = new List<double>();
[Resolved]
private ScoreProcessor scoreProcessor { get; set; }
@ -54,41 +49,20 @@ namespace osu.Game.Screens.Play.HUD
{
base.LoadComplete();
scoreProcessor.NewJudgement += onJudgementAdded;
scoreProcessor.JudgementReverted += onJudgementReverted;
}
private void onJudgementAdded(JudgementResult judgement)
{
if (!changesUnstableRate(judgement)) return;
hitOffsets.Add(judgement.TimeOffset);
scoreProcessor.NewJudgement += updateDisplay;
scoreProcessor.JudgementReverted += updateDisplay;
updateDisplay();
}
private void onJudgementReverted(JudgementResult judgement)
{
if (judgement.FailedAtJudgement || !changesUnstableRate(judgement)) return;
hitOffsets.RemoveAt(hitOffsets.Count - 1);
updateDisplay();
}
private void updateDisplay(JudgementResult _) => Scheduler.AddOnce(updateDisplay);
private void updateDisplay()
{
// At Count = 0, we get NaN, While we are allowing count = 1, it will be 0 since average = offset.
if (hitOffsets.Count > 0)
{
double mean = hitOffsets.Average();
double squares = hitOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum();
Current.Value = (int)(Math.Sqrt(squares / hitOffsets.Count) * 10);
valid.Value = true;
}
else
{
Current.Value = 0;
valid.Value = false;
}
double? unstableRate = scoreProcessor.HitEvents.CalculateUnstableRate();
valid.Value = unstableRate != null;
if (unstableRate != null)
Current.Value = (int)unstableRate.Value;
}
protected override IHasText CreateText() => new TextComponent
@ -102,8 +76,8 @@ namespace osu.Game.Screens.Play.HUD
if (scoreProcessor == null) return;
scoreProcessor.NewJudgement -= onJudgementAdded;
scoreProcessor.JudgementReverted -= onJudgementReverted;
scoreProcessor.NewJudgement -= updateDisplay;
scoreProcessor.JudgementReverted -= updateDisplay;
}
private class TextComponent : CompositeDrawable, IHasText

View File

@ -1,9 +1,7 @@
// 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.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Screens.Ranking.Statistics
@ -11,7 +9,7 @@ namespace osu.Game.Screens.Ranking.Statistics
/// <summary>
/// Displays the unstable rate statistic for a given play.
/// </summary>
public class UnstableRate : SimpleStatisticItem<double>
public class UnstableRate : SimpleStatisticItem<double?>
{
/// <summary>
/// Creates and computes an <see cref="UnstableRate"/> statistic.
@ -20,21 +18,9 @@ namespace osu.Game.Screens.Ranking.Statistics
public UnstableRate(IEnumerable<HitEvent> hitEvents)
: base("Unstable Rate")
{
double[] timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit())
.Select(ev => ev.TimeOffset).ToArray();
Value = 10 * standardDeviation(timeOffsets);
Value = hitEvents.CalculateUnstableRate();
}
private static double standardDeviation(double[] timeOffsets)
{
if (timeOffsets.Length == 0)
return double.NaN;
double mean = timeOffsets.Average();
double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum();
return Math.Sqrt(squares / timeOffsets.Length);
}
protected override string DisplayValue(double value) => double.IsNaN(value) ? "(not available)" : value.ToString("N2");
protected override string DisplayValue(double? value) => value == null ? "(not available)" : value.Value.ToString(@"N2");
}
}