From 77e853ce2593f9973b89f4386592a12fda972e1f Mon Sep 17 00:00:00 2001 From: Chinmay Patil Date: Fri, 5 Nov 2021 12:16:58 -0600 Subject: [PATCH] Optimized UR Counter and removed redundant code --- .../Gameplay/TestSceneUnstableRateCounter.cs | 90 +++++++------------ .../Screens/Play/HUD/UnstableRateCounter.cs | 83 +++++++++-------- 2 files changed, 79 insertions(+), 94 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index be1cd8a35c..0dc25cf378 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -1,21 +1,14 @@ // Copyright (c) ppy Pty Ltd . 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.Diagnostics.CodeAnalysis; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osuTK; @@ -26,23 +19,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(ScoreProcessor))] private TestScoreProcessor scoreProcessor = new TestScoreProcessor(); - [Cached(typeof(GameplayState))] - private GameplayState gameplayState; + private readonly OsuHitWindows hitWindows = new OsuHitWindows(); - private OsuHitWindows hitWindows = new OsuHitWindows(); + private UnstableRateCounter counter; private double prev; - public TestSceneUnstableRateCounter() - { - Score score = new Score - { - ScoreInfo = new ScoreInfo(), - }; - gameplayState = new GameplayState(null, null, null, score); - scoreProcessor.NewJudgement += result => scoreProcessor.PopulateScore(score.ScoreInfo); - } - [SetUpSteps] public void SetUp() { @@ -52,41 +34,38 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestBasic() { - AddStep("Create Display", () => recreateDisplay()); + AddStep("Create Display", recreateDisplay); - AddRepeatStep("Set UR to 250", () => setUR(25), 20); + // Needs multiples 2 by the nature of UR, and went for 4 to be safe. + // Creates a 250 UR by placing a +25ms then a -25ms judgement, which then results in a 250 UR + AddRepeatStep("Set UR to 250", () => applyJudgement(25, true), 4); - AddStep("Reset UR", () => + AddUntilStep("UR = 250", () => counter.Current.Value == 250.0); + + AddRepeatStep("Revert UR", () => { - scoreProcessor.Reset(); - recreateDisplay(); - }); + scoreProcessor.RevertResult( + new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) + { + TimeOffset = 25, + Type = HitResult.Perfect, + }); + }, 4); - AddRepeatStep("Set UR to 100", () => setUR(10), 20); + AddUntilStep("UR is 0", () => counter.Current.Value == 0.0); + AddUntilStep("Counter is invalid", () => counter.Child.Alpha == 0.3f); - AddStep("Reset UR", () => - { - scoreProcessor.Reset(); - recreateDisplay(); - }); - - AddRepeatStep("Set UR to 0 (+50ms offset)", () => newJudgement(50), 10); - - AddStep("Reset UR", () => - { - scoreProcessor.Reset(); - recreateDisplay(); - }); - - AddRepeatStep("Set UR to 0 (-50 offset)", () => newJudgement(-50), 10); - - AddRepeatStep("Random Judgements", () => newJudgement(), 20); + //Sets a UR of 0 by creating 10 10ms offset judgements. Since average = offset, UR = 0 + AddRepeatStep("Set UR to 0", () => applyJudgement(10, false), 10); + //Applies a UR of 100 by creating 10 -10ms offset judgements. At the 10th judgement, offset should be 100. + AddRepeatStep("Bring UR to 100", () => applyJudgement(-10, false), 10); } + private void recreateDisplay() { Clear(); - Add(new UnstableRateCounter + Add(counter = new UnstableRateCounter { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -94,24 +73,21 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private void newJudgement(double offset = 0, HitResult result = HitResult.Perfect) + private void applyJudgement(double offsetMs, bool alt) { - scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) - { - TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, - Type = result, - }); - } + double placement = offsetMs; + + if (alt) + { + placement = prev > 0 ? -offsetMs : offsetMs; + prev = placement; + } - private void setUR(double UR = 0, HitResult result = HitResult.Perfect) - { - double placement = prev > 0 ? -UR : UR; scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement()) { TimeOffset = placement, - Type = result, + Type = HitResult.Perfect, }); - prev = placement; } private class TestScoreProcessor : ScoreProcessor diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 0045dab004..7e6e3d9b39 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -4,10 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -18,8 +16,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD @@ -28,19 +24,17 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } - protected override bool IsRollingProportional => true; - protected override double RollingDuration => 750; private const float alpha_when_invalid = 0.3f; - private List hitList = new List(); + private readonly List hitOffsets = new List(); + //May be able to remove the CanBeNull as ScoreProcessor should exist everywhere, for example, in the skin editor it is cached. [CanBeNull] [Resolved(CanBeNull = true)] private ScoreProcessor scoreProcessor { get; set; } - private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); public UnstableRateCounter() { Current.Value = 0.0; @@ -56,17 +50,18 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - if (scoreProcessor != null) - { - scoreProcessor.NewJudgement += onJudgementAdded; - scoreProcessor.JudgementReverted += onJudgementChanged; - } + if (scoreProcessor == null) return; + + scoreProcessor.NewJudgement += onJudgementAdded; + scoreProcessor.JudgementReverted += onJudgementReverted; } - private bool isValid = false; + private bool isValid; + private void setValid(bool valid) { if (isValid == valid) return; + DrawableCount.FadeTo(valid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); isValid = valid; } @@ -75,39 +70,46 @@ namespace osu.Game.Screens.Play.HUD { if (!(judgement.HitObject.HitWindows is HitWindows.EmptyHitWindows) && judgement.IsHit) { - hitList.Add(judgement.TimeOffset); + hitOffsets.Add(judgement.TimeOffset); } - updateUR(); + + updateUr(); } - // Only populate via the score if the user has moved the current location. - private void onJudgementChanged(JudgementResult judgement) + // If a judgement was reverted successfully, remove the item from the hitOffsets list. + private void onJudgementReverted(JudgementResult judgement) { - ScoreInfo currentScore = new ScoreInfo(); - scoreProcessor.PopulateScore(currentScore); - hitList = currentScore.HitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()) - .Select(ev => ev.TimeOffset).ToList(); - updateUR(); + //Score Processor Conditions to revert + if (judgement.FailedAtJudgement || !judgement.Type.IsScorable()) + return; + //UR Conditions to Revert + if (judgement.HitObject.HitWindows is HitWindows.EmptyHitWindows || !judgement.IsHit) + return; + + hitOffsets.RemoveAt(hitOffsets.Count - 1); + updateUr(); } - private void updateUR() + private void updateUr() { - if (hitList.Count > 1) + // 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 = hitList.Average(); - double squares = hitList.Select(offset => Math.Pow(offset - mean, 2)).Sum(); - Current.Value = Math.Sqrt(squares / hitList.Count) * 10; + double mean = hitOffsets.Average(); + double squares = hitOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); + Current.Value = Math.Sqrt(squares / hitOffsets.Count) * 10; setValid(true); } else { + Current.Value = 0; setValid(false); } } protected override LocalisableString FormatCount(double count) { - return count.ToString("0.00"); + return count.ToString("0.00 UR"); } protected override IHasText CreateText() => new TextComponent @@ -119,12 +121,10 @@ namespace osu.Game.Screens.Play.HUD { base.Dispose(isDisposing); - if (scoreProcessor != null) - { - scoreProcessor.NewJudgement -= onJudgementAdded; - scoreProcessor.JudgementReverted -= onJudgementChanged; - } - loadCancellationSource?.Cancel(); + if (scoreProcessor == null) return; + + scoreProcessor.NewJudgement -= onJudgementAdded; + scoreProcessor.JudgementReverted -= onJudgementReverted; } private class TextComponent : CompositeDrawable, IHasText @@ -132,11 +132,12 @@ namespace osu.Game.Screens.Play.HUD public LocalisableString Text { get => intPart.Text; - set { + set + { //Not too sure about this, is there a better way to go about doing this? splitValue = value.ToString().Split('.'); intPart.Text = splitValue[0]; - decimalPart.Text = $".{splitValue[1]} UR"; + decimalPart.Text = splitValue[1]; } } @@ -159,6 +160,14 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.BottomLeft, Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), + Text = ".", + Padding = new MarginPadding { Bottom = 1.5f }, + }, decimalPart = new OsuSpriteText { Anchor = Anchor.BottomLeft,