From 91bb3f6c57d7263876d5de57bab01c5d4b444b9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 01:24:00 +0900 Subject: [PATCH 01/16] Cache argon character glyph lookups to reduce string allocations --- .../Play/HUD/ArgonCounterTextComponent.cs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index 2a3f4365cb..266dfb3301 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -137,33 +139,48 @@ namespace osu.Game.Screens.Play.HUD [BackgroundDependencyLoader] private void load(TextureStore textures) { + const string font_name = @"argon-counter"; + Spacing = new Vector2(-2f, 0f); - Font = new FontUsage(@"argon-counter", 1); - glyphStore = new GlyphStore(textures, getLookup); + Font = new FontUsage(font_name, 1); + glyphStore = new GlyphStore(font_name, textures, getLookup); } protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); private class GlyphStore : ITexturedGlyphLookupStore { + private readonly string fontName; private readonly TextureStore textures; private readonly Func getLookup; - public GlyphStore(TextureStore textures, Func getLookup) + private readonly Dictionary cache = new Dictionary(); + + public GlyphStore(string fontName, TextureStore textures, Func getLookup) { + this.fontName = fontName; this.textures = textures; this.getLookup = getLookup; } public ITexturedCharacterGlyph? Get(string? fontName, char character) { + // We only service one font. + Debug.Assert(fontName == this.fontName); + + if (cache.TryGetValue(character, out var cached)) + return cached; + string lookup = getLookup(character); var texture = textures.Get($"Gameplay/Fonts/{fontName}-{lookup}"); - if (texture == null) - return null; + TexturedCharacterGlyph? glyph = null; - return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 0.125f); + if (texture != null) + glyph = new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 0.125f); + + cache[character] = glyph; + return glyph; } public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); From 5b55ca66920b40b394b0403f92b7f371c35cf6b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 02:26:26 +0900 Subject: [PATCH 02/16] Cache legacy skin character glyph lookups to reduce string allocations --- osu.Game/Skinning/LegacySpriteText.cs | 35 +++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 8aefa50252..81db5fdf36 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; @@ -44,10 +46,11 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin) { - base.Font = new FontUsage(skin.GetFontPrefix(font), 1, fixedWidth: FixedWidth); + string fontPrefix = skin.GetFontPrefix(font); + base.Font = new FontUsage(fontPrefix, 1, fixedWidth: FixedWidth); Spacing = new Vector2(-skin.GetFontOverlap(font), 0); - glyphStore = new LegacyGlyphStore(skin, MaxSizePerGlyph); + glyphStore = new LegacyGlyphStore(fontPrefix, skin, MaxSizePerGlyph); } protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); @@ -57,25 +60,41 @@ namespace osu.Game.Skinning private readonly ISkin skin; private readonly Vector2? maxSize; - public LegacyGlyphStore(ISkin skin, Vector2? maxSize) + private readonly string fontName; + + private readonly Dictionary cache = new Dictionary(); + + public LegacyGlyphStore(string fontName, ISkin skin, Vector2? maxSize) { + this.fontName = fontName; this.skin = skin; this.maxSize = maxSize; } public ITexturedCharacterGlyph? Get(string? fontName, char character) { + // We only service one font. + Debug.Assert(fontName == this.fontName); + + if (cache.TryGetValue(character, out var cached)) + return cached; + string lookup = getLookupName(character); var texture = skin.GetTexture($"{fontName}-{lookup}"); - if (texture == null) - return null; + TexturedCharacterGlyph? glyph = null; - if (maxSize != null) - texture = texture.WithMaximumSize(maxSize.Value); + if (texture != null) + { + if (maxSize != null) + texture = texture.WithMaximumSize(maxSize.Value); - return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 1f / texture.ScaleAdjust); + glyph = new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 1f / texture.ScaleAdjust); + } + + cache[character] = glyph; + return glyph; } private static string getLookupName(char character) From e9289cfbe78f318d00c9b77065daca6fa824cb09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 01:51:20 +0900 Subject: [PATCH 03/16] Reduce precision of audio balance adjustments during slider sliding --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1daaa24d57..bce28361cb 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -599,7 +599,9 @@ namespace osu.Game.Rulesets.Objects.Drawables float balanceAdjustAmount = positionalHitsoundsLevel.Value * 2; double returnedValue = balanceAdjustAmount * (position - 0.5f); - return returnedValue; + // Rounded to reduce the overhead of audio adjustments (which are currently bindable heavy). + // Balance is very hard to perceive in small increments anyways. + return Math.Round(returnedValue, 2); } /// From b809d4c068f77558e30e80e0a9a8846cd41af5c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 02:14:03 +0900 Subject: [PATCH 04/16] Remove delegate overhead from argon health display's animation updates --- osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs | 16 ++++++++++++---- osu.Game/Screens/Play/HUD/HealthDisplay.cs | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 8acc43c091..7721f9c0c0 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -68,11 +69,11 @@ namespace osu.Game.Screens.Play.HUD get => glowBarValue; set { - if (glowBarValue == value) + if (Precision.AlmostEquals(glowBarValue, value, 0.0001)) return; glowBarValue = value; - Scheduler.AddOnce(updatePathVertices); + pathVerticesCache.Invalidate(); } } @@ -83,11 +84,11 @@ namespace osu.Game.Screens.Play.HUD get => healthBarValue; set { - if (healthBarValue == value) + if (Precision.AlmostEquals(healthBarValue, value, 0.0001)) return; healthBarValue = value; - Scheduler.AddOnce(updatePathVertices); + pathVerticesCache.Invalidate(); } } @@ -100,6 +101,8 @@ namespace osu.Game.Screens.Play.HUD private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize); + private readonly Cached pathVerticesCache = new Cached(); + public ArgonHealthDisplay() { AddLayout(drawSizeLayout); @@ -208,6 +211,9 @@ namespace osu.Game.Screens.Play.HUD drawSizeLayout.Validate(); } + if (!pathVerticesCache.IsValid) + updatePathVertices(); + mainBar.Alpha = (float)Interpolation.DampContinuously(mainBar.Alpha, Current.Value > 0 ? 1 : 0, 40, Time.Elapsed); glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed); } @@ -346,6 +352,8 @@ namespace osu.Game.Screens.Play.HUD mainBar.Vertices = healthBarVertices.Select(v => v - healthBarVertices[0]).ToList(); mainBar.Position = healthBarVertices[0]; + + pathVerticesCache.Validate(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index 7747036826..54aba6b8da 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Play.HUD public Bindable Current { get; } = new BindableDouble { MinValue = 0, - MaxValue = 1 + MaxValue = 1, }; private BindableNumber health = null!; From 9d9e6fcfdbc3c21faa7e637375ac4a9e4cdf0c51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 03:22:19 +0900 Subject: [PATCH 05/16] Remove LINQ calls in hot paths --- osu.Game/Skinning/SkinnableSound.cs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index f866a4f8ec..f153f4f8d3 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -194,9 +194,33 @@ namespace osu.Game.Skinning /// /// Whether any samples are currently playing. /// - public bool IsPlaying => samplesContainer.Any(s => s.Playing); + public bool IsPlaying + { + get + { + foreach (PoolableSkinnableSample s in samplesContainer) + { + if (s.Playing) + return true; + } - public bool IsPlayed => samplesContainer.Any(s => s.Played); + return false; + } + } + + public bool IsPlayed + { + get + { + foreach (PoolableSkinnableSample s in samplesContainer) + { + if (s.Played) + return true; + } + + return false; + } + } public IBindable AggregateVolume => samplesContainer.AggregateVolume; From 35eff639cb936188f73c34dc71e67ea4d93a061d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 03:25:00 +0900 Subject: [PATCH 06/16] Remove unnecessary second iteration over `NestedHitObjects` --- .../Objects/Drawables/DrawableSlider.cs | 7 ++----- .../Objects/Drawables/DrawableSliderRepeat.cs | 2 +- .../Objects/Drawables/ITrackSnaking.cs | 15 --------------- 3 files changed, 3 insertions(+), 21 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index b306fd38c1..0f8c9a4d36 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -246,11 +246,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Ball.UpdateProgress(completionProgress); SliderBody?.UpdateProgress(HeadCircle.IsHit ? completionProgress : 0); - foreach (DrawableHitObject hitObject in NestedHitObjects) - { - if (hitObject is ITrackSnaking s) - s.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0)); - } + foreach (DrawableSliderRepeat repeat in repeatContainer) + repeat.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0)); Size = SliderBody?.Size ?? Vector2.Zero; OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index c6d4f7c4ca..3239565528 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -17,7 +17,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public partial class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking + public partial class DrawableSliderRepeat : DrawableOsuHitObject { public new SliderRepeat HitObject => (SliderRepeat)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs deleted file mode 100644 index cae2a7c36d..0000000000 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osuTK; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables -{ - /// - /// A component which tracks the current end snaking position of a slider. - /// - public interface ITrackSnaking - { - void UpdateSnakingPosition(Vector2 start, Vector2 end); - } -} From 5cc4a586acf40fd3d9be3425f875e54406a38c4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jan 2024 03:26:58 +0900 Subject: [PATCH 07/16] Avoid iteration over `NestedHitObjects` in silder's `Update` unless necessary --- .../Objects/Drawables/DrawableSlider.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 0f8c9a4d36..c5194025c1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -36,6 +36,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private ShakeContainer shakeContainer; + private Vector2? childAnchorPosition; + protected override IEnumerable DimmablePieces => new Drawable[] { HeadCircle, @@ -254,10 +256,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (DrawSize != Vector2.Zero) { - var childAnchorPosition = Vector2.Divide(OriginPosition, DrawSize); - foreach (var obj in NestedHitObjects) - obj.RelativeAnchorPosition = childAnchorPosition; - Ball.RelativeAnchorPosition = childAnchorPosition; + Vector2 pos = Vector2.Divide(OriginPosition, DrawSize); + + if (pos != childAnchorPosition) + { + childAnchorPosition = pos; + foreach (var obj in NestedHitObjects) + obj.RelativeAnchorPosition = pos; + Ball.RelativeAnchorPosition = pos; + } } } From 16ea7f9b7787462aa1506bac4ecb0c57090c630d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 7 Jan 2024 13:10:50 +0900 Subject: [PATCH 08/16] Avoid completely unnecessary string allocations in `ArgonCounterTextComponent` --- .../Screens/Play/HUD/ArgonCounterTextComponent.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index 266dfb3301..9d364acd59 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -35,14 +34,7 @@ namespace osu.Game.Screens.Play.HUD public LocalisableString Text { get => textPart.Text; - set - { - int remainingCount = RequiredDisplayDigits.Value - value.ToString().Count(char.IsDigit); - string remainingText = remainingCount > 0 ? new string('#', remainingCount) : string.Empty; - - wireframesPart.Text = remainingText + value; - textPart.Text = value; - } + set => textPart.Text = value; } public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null) @@ -83,6 +75,8 @@ namespace osu.Game.Screens.Play.HUD } } }; + + RequiredDisplayDigits.BindValueChanged(digits => wireframesPart.Text = new string('#', digits.NewValue)); } private string textLookup(char c) From dc31c66f6229445dd0ebd0d6a6a52c7241da59f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 7 Jan 2024 19:49:42 +0900 Subject: [PATCH 09/16] Return null on font lookup failure instead of asserting Fallback weirdness. --- osu.Game/Skinning/LegacySpriteText.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 81db5fdf36..581e7534e4 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; @@ -74,7 +73,8 @@ namespace osu.Game.Skinning public ITexturedCharacterGlyph? Get(string? fontName, char character) { // We only service one font. - Debug.Assert(fontName == this.fontName); + if (fontName != this.fontName) + return null; if (cache.TryGetValue(character, out var cached)) return cached; From 962c8ba4acfc8920633f99c022824d4d58564ef9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 7 Jan 2024 20:55:28 +0900 Subject: [PATCH 10/16] Reset child anchor position cache on hitobject position change --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index c5194025c1..6cc8b8e935 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -121,7 +121,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } }); - PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); + PositionBindable.BindValueChanged(_ => + { + Position = HitObject.StackedPosition; + childAnchorPosition = null; + }); StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); ScaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue)); From 3f5899dae041e66d8292acc757e25556a2172926 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 14:05:59 +0900 Subject: [PATCH 11/16] Fix incorrect implementation of wireframe digits --- .../Play/HUD/ArgonCounterTextComponent.cs | 1 - .../Screens/Play/HUD/ArgonScoreCounter.cs | 36 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index 9d364acd59..f88874f872 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; diff --git a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs index 005f7e36a7..f7ca218767 100644 --- a/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonScoreCounter.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -15,6 +16,8 @@ namespace osu.Game.Screens.Play.HUD { public partial class ArgonScoreCounter : GameplayScoreCounter, ISerialisableDrawable { + private ArgonScoreTextComponent scoreText = null!; + protected override double RollingDuration => 500; protected override Easing RollingEasing => Easing.OutQuint; @@ -33,13 +36,42 @@ namespace osu.Game.Screens.Play.HUD protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(); - protected override IHasText CreateText() => new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper()) + protected override IHasText CreateText() => scoreText = new ArgonScoreTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeadersScore.ToUpper()) { - RequiredDisplayDigits = { BindTarget = RequiredDisplayDigits }, WireframeOpacity = { BindTarget = WireframeOpacity }, ShowLabel = { BindTarget = ShowLabel }, }; + public ArgonScoreCounter() + { + RequiredDisplayDigits.BindValueChanged(_ => updateWireframe()); + } + + public override long DisplayedCount + { + get => base.DisplayedCount; + set + { + base.DisplayedCount = value; + updateWireframe(); + } + } + + private void updateWireframe() + { + scoreText.RequiredDisplayDigits.Value = + Math.Max(RequiredDisplayDigits.Value, getDigitsRequiredForDisplayCount()); + } + + private int getDigitsRequiredForDisplayCount() + { + int digitsRequired = 1; + long c = DisplayedCount; + while ((c /= 10) > 0) + digitsRequired++; + return digitsRequired; + } + private partial class ArgonScoreTextComponent : ArgonCounterTextComponent { public ArgonScoreTextComponent(Anchor anchor, LocalisableString? label = null) From 765d41faa9940e8ae5878babe326cc76f39ba930 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 14:06:59 +0900 Subject: [PATCH 12/16] Change second occurrence of debug.assert with early return for fallback safety --- osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs index f88874f872..a11f2f01cd 100644 --- a/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs +++ b/osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs @@ -159,7 +159,8 @@ namespace osu.Game.Screens.Play.HUD public ITexturedCharacterGlyph? Get(string? fontName, char character) { // We only service one font. - Debug.Assert(fontName == this.fontName); + if (fontName != this.fontName) + return null; if (cache.TryGetValue(character, out var cached)) return cached; From 5970a68e2d12f4f8fc3a118cf68da7a69c1f1fcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 14:17:33 +0900 Subject: [PATCH 13/16] Use invalidation based logic for child anchor position updpates in `DrawableSlider` --- .../Objects/Drawables/DrawableSlider.cs | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 6cc8b8e935..4099d47d61 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Layout; using osu.Game.Audio; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Objects; @@ -36,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private ShakeContainer shakeContainer; - private Vector2? childAnchorPosition; - protected override IEnumerable DimmablePieces => new Drawable[] { HeadCircle, @@ -68,6 +67,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Container repeatContainer; private PausableSkinnableSound slidingSample; + private readonly LayoutValue drawSizeLayout; + public DrawableSlider() : this(null) { @@ -84,6 +85,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AlwaysPresent = true, Alpha = 0 }; + AddLayout(drawSizeLayout = new LayoutValue(Invalidation.DrawSize | Invalidation.MiscGeometry)); } [BackgroundDependencyLoader] @@ -121,11 +123,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } }); - PositionBindable.BindValueChanged(_ => - { - Position = HitObject.StackedPosition; - childAnchorPosition = null; - }); + PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); ScaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue)); @@ -258,17 +256,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Size = SliderBody?.Size ?? Vector2.Zero; OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero; - if (DrawSize != Vector2.Zero) + if (!drawSizeLayout.IsValid) { Vector2 pos = Vector2.Divide(OriginPosition, DrawSize); + foreach (var obj in NestedHitObjects) + obj.RelativeAnchorPosition = pos; + Ball.RelativeAnchorPosition = pos; - if (pos != childAnchorPosition) - { - childAnchorPosition = pos; - foreach (var obj in NestedHitObjects) - obj.RelativeAnchorPosition = pos; - Ball.RelativeAnchorPosition = pos; - } + drawSizeLayout.Validate(); } } From 4110adc4c0a9fdc763ec73061169a5e4e8952a7c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2024 20:16:27 +0900 Subject: [PATCH 14/16] Fix missing wireframe on argon combo counter --- .../Screens/Play/HUD/ArgonAccuracyCounter.cs | 1 + .../Screens/Play/HUD/ArgonComboCounter.cs | 24 +++++++++++++++++++ osu.Game/Screens/Play/HUD/ComboCounter.cs | 5 ---- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index 521ad63426..5284e3167a 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -83,6 +83,7 @@ namespace osu.Game.Screens.Play.HUD }, fractionPart = new ArgonCounterTextComponent(Anchor.TopLeft) { + RequiredDisplayDigits = { Value = 2 }, WireframeOpacity = { BindTarget = WireframeOpacity }, Scale = new Vector2(0.5f), }, diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 5ea7fd0b82..af884aa441 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -57,6 +57,30 @@ namespace osu.Game.Screens.Play.HUD }); } + public override int DisplayedCount + { + get => base.DisplayedCount; + set + { + base.DisplayedCount = value; + updateWireframe(); + } + } + + private void updateWireframe() + { + text.RequiredDisplayDigits.Value = getDigitsRequiredForDisplayCount(); + } + + private int getDigitsRequiredForDisplayCount() + { + int digitsRequired = 1; + long c = DisplayedCount; + while ((c /= 10) > 0) + digitsRequired++; + return digitsRequired; + } + protected override LocalisableString FormatCount(int count) => $@"{count}x"; protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper()) diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs index 17531281aa..93802e11c2 100644 --- a/osu.Game/Screens/Play/HUD/ComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboCounter.cs @@ -11,11 +11,6 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } - protected ComboCounter() - { - Current.Value = DisplayedCount = 0; - } - protected override double GetProportionalDuration(int currentValue, int newValue) { return Math.Abs(currentValue - newValue) * RollingDuration * 100.0f; From 77bf6e3244111b026f930a75fbb35dd497d9c921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jan 2024 13:59:27 +0100 Subject: [PATCH 15/16] Fix missing wireframe behind "x" sign on combo counter display --- osu.Game/Screens/Play/HUD/ArgonComboCounter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index af884aa441..1d6ca3c893 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -74,7 +74,8 @@ namespace osu.Game.Screens.Play.HUD private int getDigitsRequiredForDisplayCount() { - int digitsRequired = 1; + // one for the single presumed starting digit, one for the "x" at the end. + int digitsRequired = 2; long c = DisplayedCount; while ((c /= 10) > 0) digitsRequired++; From 92ba77031403632ad03b86efcbd2754a47b6c646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jan 2024 14:00:58 +0100 Subject: [PATCH 16/16] Fix missing wireframe behind percent sign on accuracy counter --- osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs index 5284e3167a..171aa3f44b 100644 --- a/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs @@ -90,6 +90,7 @@ namespace osu.Game.Screens.Play.HUD percentText = new ArgonCounterTextComponent(Anchor.TopLeft) { Text = @"%", + RequiredDisplayDigits = { Value = 1 }, WireframeOpacity = { BindTarget = WireframeOpacity } }, }