From b60e08ee46e361e1bb0ddfa65452702a9e26f219 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jun 2026 18:33:18 +0900 Subject: [PATCH] Avoid bindable overheads in `OsuHitObject.StackHeight` implementation We made this `HitObjectProperty` class quite a while back with the concept of avoiding bindable initialisation when they are never going to be used. Unfortunately there are multiple cases where bindables are accessed to bind event flows which break this. This one is easy to fix, so I've done so. The other remaining case is more egregious and I can't thing of a ten-minute-fix for it. If anyone has ideas, please take a look: https://github.com/ppy/osu/blob/8b542e5442f42ad132af0414a4f7191df9718a99/osu.Game/Rulesets/Objects/HitObject.cs#L123-L125 --- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 32 +++++++++++-------- .../Rulesets/Objects/HitObjectProperty.cs | 5 +++ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 0a5365e652eb..0bdbce13d383 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -81,7 +81,25 @@ public float Y private HitObjectProperty stackHeight; - public Bindable StackHeightBindable => stackHeight.Bindable; + public Bindable StackHeightBindable + { + get + { + if (stackHeight.BindableCreated) + return stackHeight.Bindable; + + stackHeight.Bindable.BindValueChanged(height => + { + foreach (var nested in NestedHitObjects) + { + if (nested is OsuHitObject osuHitObject) + osuHitObject.StackHeight = height.NewValue; + } + }); + + return stackHeight.Bindable; + } + } public int StackHeight { @@ -155,18 +173,6 @@ public bool LastInCombo set => lastInCombo.Value = value; } - protected OsuHitObject() - { - StackHeightBindable.BindValueChanged(height => - { - foreach (var nested in NestedHitObjects) - { - if (nested is OsuHitObject osuHitObject) - osuHitObject.StackHeight = height.NewValue; - } - }); - } - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game/Rulesets/Objects/HitObjectProperty.cs b/osu.Game/Rulesets/Objects/HitObjectProperty.cs index f1df83f80c76..d3103e6d6e96 100644 --- a/osu.Game/Rulesets/Objects/HitObjectProperty.cs +++ b/osu.Game/Rulesets/Objects/HitObjectProperty.cs @@ -26,6 +26,11 @@ public struct HitObjectProperty /// public Bindable Bindable => backingBindable ??= new Bindable(defaultValue) { Value = backingValue }; + /// + /// Whether the backing bindable has already been created. + /// + public bool BindableCreated => backingBindable != null; + /// /// The current value, derived from and delegated to if initialised, or a temporary field otherwise. ///