From 5668258182537bdfaa6d6419e75a3d6f497e1d86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Nov 2024 19:43:42 +0900 Subject: [PATCH] Add incremental processing --- osu.Game.Benchmarks/BenchmarkUnstableRate.cs | 26 ++++++++++-- .../NonVisual/Ranking/UnstableRateTest.cs | 42 ++++++++++++++++++- .../Rulesets/Scoring/HitEventExtensions.cs | 14 ++++--- .../Screens/Play/HUD/UnstableRateCounter.cs | 2 +- .../Ranking/Statistics/UnstableRate.cs | 2 +- 5 files changed, 74 insertions(+), 12 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkUnstableRate.cs b/osu.Game.Benchmarks/BenchmarkUnstableRate.cs index 7b6c839648..4d3023e92e 100644 --- a/osu.Game.Benchmarks/BenchmarkUnstableRate.cs +++ b/osu.Game.Benchmarks/BenchmarkUnstableRate.cs @@ -13,27 +13,45 @@ namespace osu.Game.Benchmarks { public class BenchmarkUnstableRate : BenchmarkTest { - private List events = null!; + private readonly List> incrementalEventLists = new List>(); public override void SetUp() { base.SetUp(); - events = new List(); + + var events = new List(); for (int i = 0; i < 2048; i++) { // Ensure the object has hit windows populated. var hitObject = new HitCircle(); hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - events.Add(new HitEvent(RNG.NextDouble(-200.0, 200.0), RNG.NextDouble(1.0, 2.0), HitResult.Great, hitObject, null, null)); + + incrementalEventLists.Add(new List(events)); } } [Benchmark] public void CalculateUnstableRate() { - _ = events.CalculateUnstableRate(); + for (int i = 0; i < 2048; i++) + { + var events = incrementalEventLists[i]; + _ = events.CalculateUnstableRate(); + } + } + + [Benchmark] + public void CalculateUnstableRateUsingIncrementalCalculation() + { + HitEventExtensions.UnstableRateCalculationResult? last = null; + + for (int i = 0; i < 2048; i++) + { + var events = incrementalEventLists[i]; + last = events.CalculateUnstableRate(last); + } } } } diff --git a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs index 94a0e34d0d..03dc91b5d4 100644 --- a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs +++ b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs @@ -26,7 +26,47 @@ namespace osu.Game.Tests.NonVisual.Ranking var unstableRate = new UnstableRate(events); Assert.IsNotNull(unstableRate.Value); - Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value.Value, 10 * Math.Sqrt(10))); + Assert.AreEqual(unstableRate.Value.Value, 10 * Math.Sqrt(10), Precision.DOUBLE_EPSILON); + } + + [Test] + public void TestDistributedHitsIncrementalRewind() + { + var events = Enumerable.Range(-5, 11) + .Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null)) + .ToList(); + + HitEventExtensions.UnstableRateCalculationResult result = null; + + for (int i = 0; i < events.Count; i++) + { + result = events.GetRange(0, i + 1) + .CalculateUnstableRate(result); + } + + result = events.GetRange(0, 2).CalculateUnstableRate(result); + + Assert.IsNotNull(result!.Result); + Assert.AreEqual(5, result.Result, Precision.DOUBLE_EPSILON); + } + + [Test] + public void TestDistributedHitsIncremental() + { + var events = Enumerable.Range(-5, 11) + .Select(t => new HitEvent(t - 5, 1.0, HitResult.Great, new HitObject(), null, null)) + .ToList(); + + HitEventExtensions.UnstableRateCalculationResult result = null; + + for (int i = 0; i < events.Count; i++) + { + result = events.GetRange(0, i + 1) + .CalculateUnstableRate(result); + } + + Assert.IsNotNull(result!.Result); + Assert.AreEqual(10 * Math.Sqrt(10), result.Result, Precision.DOUBLE_EPSILON); } [Test] diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index e79504d1ec..115ffb67f7 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -20,16 +20,18 @@ namespace osu.Game.Rulesets.Scoring /// A non-null value if unstable rate could be calculated, /// and if unstable rate cannot be calculated due to being empty. /// - public static double? CalculateUnstableRate(this IReadOnlyList hitEvents) + public static UnstableRateCalculationResult? CalculateUnstableRate(this IReadOnlyList hitEvents, UnstableRateCalculationResult? previousResult = null) { Debug.Assert(hitEvents.All(ev => ev.GameplayRate != null)); int count = 0; - double mean = 0; - double sumOfSquares = 0; + double mean = previousResult?.Mean ?? 0; + double sumOfSquares = previousResult?.SumOfSquares ?? 0; - foreach (var e in hitEvents) + for (int i = previousResult?.CalculatedHitEventsCount - 1 ?? 0; i < hitEvents.Count; i++) { + HitEvent e = hitEvents[i]; + if (!AffectsUnstableRate(e)) continue; @@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Scoring if (count == 0) return null; - return 10.0 * Math.Sqrt(sumOfSquares / count); + return new UnstableRateCalculationResult(hitEvents.Count, sumOfSquares, mean, 10.0 * Math.Sqrt(sumOfSquares / count)); } /// @@ -66,5 +68,7 @@ namespace osu.Game.Rulesets.Scoring } public static bool AffectsUnstableRate(HitEvent e) => e.HitObject.HitWindows != HitWindows.Empty && e.Result.IsHit(); + + public record UnstableRateCalculationResult(int CalculatedHitEventsCount, double SumOfSquares, double Mean, double Result); } } diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 3c9ab87022..db271a21c5 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Play.HUD private void updateDisplay() { - double? unstableRate = scoreProcessor.HitEvents.CalculateUnstableRate(); + double? unstableRate = scoreProcessor.HitEvents.CalculateUnstableRate()?.Result; valid.Value = unstableRate != null; if (unstableRate != null) diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs index 10b18d09c9..d114bed156 100644 --- a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Ranking.Statistics public UnstableRate(IReadOnlyList hitEvents) : base("Unstable Rate") { - Value = hitEvents.CalculateUnstableRate(); + Value = hitEvents.CalculateUnstableRate()?.Result; } protected override string DisplayValue(double? value) => value == null ? "(not available)" : value.Value.ToString(@"N2");