From 716bb5abf3e9763401da17508a3734f971ee020b Mon Sep 17 00:00:00 2001 From: StanR Date: Sat, 24 Jan 2026 15:24:37 +0500 Subject: [PATCH 1/3] Simplify ExtendedDifficultyCalculators usage --- .../ExtendedCatchDifficultyCalculator.cs | 17 +++++++++---- .../ExtendedManiaDifficultyCalculator.cs | 17 +++++++++---- .../ExtendedOsuDifficultyCalculator.cs | 17 +++++++++---- .../ExtendedTaikoDifficultyCalculator.cs | 17 +++++++++---- .../IExtendedDifficultyCalculator.cs | 3 +-- .../CatchObjectInspectorRuleset.cs | 23 ++++++++++++++---- .../ObjectInspection/ObjectInspector.cs | 13 +++------- .../OsuObjectInspectorRuleset.cs | 22 ++++++++++++++--- .../TaikoObjectInspectorRuleset.cs | 24 +++++++++++++++---- 9 files changed, 109 insertions(+), 44 deletions(-) diff --git a/PerformanceCalculatorGUI/ExtendedCatchDifficultyCalculator.cs b/PerformanceCalculatorGUI/ExtendedCatchDifficultyCalculator.cs index d7b65252d1..665b1e33a1 100644 --- a/PerformanceCalculatorGUI/ExtendedCatchDifficultyCalculator.cs +++ b/PerformanceCalculatorGUI/ExtendedCatchDifficultyCalculator.cs @@ -1,11 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch.Difficulty; -using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; @@ -15,6 +15,7 @@ namespace PerformanceCalculatorGUI public class ExtendedCatchDifficultyCalculator : CatchDifficultyCalculator, IExtendedDifficultyCalculator { private Skill[] skills = []; + private DifficultyHitObject[] difficultyHitObjects = []; public ExtendedCatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -22,12 +23,18 @@ public ExtendedCatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap b } public Skill[] GetSkills() => skills; - public DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate) => CreateDifficultyHitObjects(beatmap, clockRate).ToArray(); + public DifficultyHitObject[] GetDifficultyHitObjects() => difficultyHitObjects; - protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - this.skills = skills; - return base.CreateDifficultyAttributes(beatmap, mods, skills, clockRate); + difficultyHitObjects = base.CreateDifficultyHitObjects(beatmap, clockRate).ToArray(); + return difficultyHitObjects; + } + + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) + { + skills = base.CreateSkills(beatmap, mods, clockRate); + return skills; } } } diff --git a/PerformanceCalculatorGUI/ExtendedManiaDifficultyCalculator.cs b/PerformanceCalculatorGUI/ExtendedManiaDifficultyCalculator.cs index 9126c6b8b5..ca68aa8763 100644 --- a/PerformanceCalculatorGUI/ExtendedManiaDifficultyCalculator.cs +++ b/PerformanceCalculatorGUI/ExtendedManiaDifficultyCalculator.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mania.Difficulty; @@ -15,6 +15,7 @@ namespace PerformanceCalculatorGUI public class ExtendedManiaDifficultyCalculator : ManiaDifficultyCalculator, IExtendedDifficultyCalculator { private Skill[] skills = []; + private DifficultyHitObject[] difficultyHitObjects = []; public ExtendedManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -22,12 +23,18 @@ public ExtendedManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap b } public Skill[] GetSkills() => skills; - public DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate) => CreateDifficultyHitObjects(beatmap, clockRate).ToArray(); + public DifficultyHitObject[] GetDifficultyHitObjects() => difficultyHitObjects; - protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - this.skills = skills; - return base.CreateDifficultyAttributes(beatmap, mods, skills, clockRate); + difficultyHitObjects = base.CreateDifficultyHitObjects(beatmap, clockRate).ToArray(); + return difficultyHitObjects; + } + + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) + { + skills = base.CreateSkills(beatmap, mods, clockRate); + return skills; } } } diff --git a/PerformanceCalculatorGUI/ExtendedOsuDifficultyCalculator.cs b/PerformanceCalculatorGUI/ExtendedOsuDifficultyCalculator.cs index 5c9c8c5785..4ee2189eba 100644 --- a/PerformanceCalculatorGUI/ExtendedOsuDifficultyCalculator.cs +++ b/PerformanceCalculatorGUI/ExtendedOsuDifficultyCalculator.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; @@ -15,6 +15,7 @@ namespace PerformanceCalculatorGUI public class ExtendedOsuDifficultyCalculator : OsuDifficultyCalculator, IExtendedDifficultyCalculator { private Skill[] skills = []; + private DifficultyHitObject[] difficultyHitObjects = []; public ExtendedOsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -22,12 +23,18 @@ public ExtendedOsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap bea } public Skill[] GetSkills() => skills; - public DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate) => CreateDifficultyHitObjects(beatmap, clockRate).ToArray(); + public DifficultyHitObject[] GetDifficultyHitObjects() => difficultyHitObjects; - protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - this.skills = skills; - return base.CreateDifficultyAttributes(beatmap, mods, skills, clockRate); + difficultyHitObjects = base.CreateDifficultyHitObjects(beatmap, clockRate).ToArray(); + return difficultyHitObjects; + } + + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) + { + skills = base.CreateSkills(beatmap, mods, clockRate); + return skills; } } } diff --git a/PerformanceCalculatorGUI/ExtendedTaikoDifficultyCalculator.cs b/PerformanceCalculatorGUI/ExtendedTaikoDifficultyCalculator.cs index 8c30700c4c..60f29017ec 100644 --- a/PerformanceCalculatorGUI/ExtendedTaikoDifficultyCalculator.cs +++ b/PerformanceCalculatorGUI/ExtendedTaikoDifficultyCalculator.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; @@ -15,6 +15,7 @@ namespace PerformanceCalculatorGUI public class ExtendedTaikoDifficultyCalculator : TaikoDifficultyCalculator, IExtendedDifficultyCalculator { private Skill[] skills = []; + private DifficultyHitObject[] difficultyHitObjects = []; public ExtendedTaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -22,12 +23,18 @@ public ExtendedTaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap b } public Skill[] GetSkills() => skills; - public DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate) => CreateDifficultyHitObjects(beatmap, clockRate).ToArray(); + public DifficultyHitObject[] GetDifficultyHitObjects() => difficultyHitObjects; - protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - this.skills = skills; - return base.CreateDifficultyAttributes(beatmap, mods, skills, clockRate); + difficultyHitObjects = base.CreateDifficultyHitObjects(beatmap, clockRate).ToArray(); + return difficultyHitObjects; + } + + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) + { + skills = base.CreateSkills(beatmap, mods, clockRate); + return skills; } } } diff --git a/PerformanceCalculatorGUI/IExtendedDifficultyCalculator.cs b/PerformanceCalculatorGUI/IExtendedDifficultyCalculator.cs index 787502c470..f40fb45278 100644 --- a/PerformanceCalculatorGUI/IExtendedDifficultyCalculator.cs +++ b/PerformanceCalculatorGUI/IExtendedDifficultyCalculator.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; @@ -10,6 +9,6 @@ namespace PerformanceCalculatorGUI public interface IExtendedDifficultyCalculator { Skill[] GetSkills(); - DifficultyHitObject[] GetDifficultyHitObjects(IBeatmap beatmap, double clockRate); + DifficultyHitObject[] GetDifficultyHitObjects(); } } diff --git a/PerformanceCalculatorGUI/Screens/ObjectInspection/CatchObjectInspectorRuleset.cs b/PerformanceCalculatorGUI/Screens/ObjectInspection/CatchObjectInspectorRuleset.cs index 1bcc8a3bd8..0497f44559 100644 --- a/PerformanceCalculatorGUI/Screens/ObjectInspection/CatchObjectInspectorRuleset.cs +++ b/PerformanceCalculatorGUI/Screens/ObjectInspection/CatchObjectInspectorRuleset.cs @@ -4,26 +4,41 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Catch.Edit; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; namespace PerformanceCalculatorGUI.Screens.ObjectInspection { public partial class CatchObjectInspectorRuleset : DrawableCatchEditorRuleset { - private readonly CatchDifficultyHitObject[] difficultyHitObjects; + private CatchDifficultyHitObject[] difficultyHitObjects = []; [Resolved] private ObjectDifficultyValuesContainer objectDifficultyValuesContainer { get; set; } = null!; - public CatchObjectInspectorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods, ExtendedCatchDifficultyCalculator difficultyCalculator, double clockRate) + [Resolved] + private Bindable difficultyCalculator { get; set; } = null!; + + public CatchObjectInspectorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { - difficultyHitObjects = difficultyCalculator.GetDifficultyHitObjects(beatmap, clockRate) - .Cast().ToArray(); + } + + protected override void LoadComplete() + { + var extendedDifficultyCalculator = (IExtendedDifficultyCalculator?)difficultyCalculator.Value; + + if (extendedDifficultyCalculator != null) + { + difficultyHitObjects = extendedDifficultyCalculator.GetDifficultyHitObjects().Cast().ToArray(); + } + + base.LoadComplete(); } public override bool PropagatePositionalInputSubTree => false; diff --git a/PerformanceCalculatorGUI/Screens/ObjectInspection/ObjectInspector.cs b/PerformanceCalculatorGUI/Screens/ObjectInspection/ObjectInspector.cs index 17b2e5b1c4..98f9768dc9 100644 --- a/PerformanceCalculatorGUI/Screens/ObjectInspection/ObjectInspector.cs +++ b/PerformanceCalculatorGUI/Screens/ObjectInspection/ObjectInspector.cs @@ -18,7 +18,6 @@ using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; -using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -54,9 +53,6 @@ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnl [Resolved] private Bindable ruleset { get; set; } = null!; - [Resolved] - private Bindable difficultyCalculator { get; set; } = null!; - private readonly ProcessorWorkingBeatmap processorBeatmap; private EditorClock clock = null!; private Container rulesetContainer = null!; @@ -205,8 +201,7 @@ private void load(OverlayColourProvider colourProvider) RelativeSizeAxes = Axes.Both, PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners } }, - new OsuObjectInspectorRuleset(rulesetInstance, playableBeatmap, modifiedMods!, (difficultyCalculator.Value as ExtendedOsuDifficultyCalculator)!, - processorBeatmap.Track.Rate) + new OsuObjectInspectorRuleset(rulesetInstance, playableBeatmap, modifiedMods!) { RelativeSizeAxes = Axes.Both, Clock = clock, @@ -216,8 +211,7 @@ private void load(OverlayColourProvider colourProvider) }, "taiko" => new TaikoPlayfieldAdjustmentContainer { - Child = new TaikoObjectInspectorRuleset(rulesetInstance, playableBeatmap, modifiedMods!, (difficultyCalculator.Value as ExtendedTaikoDifficultyCalculator)!, - processorBeatmap.Track.Rate) + Child = new TaikoObjectInspectorRuleset(rulesetInstance, playableBeatmap, modifiedMods!) { RelativeSizeAxes = Axes.Both, Clock = clock, @@ -230,8 +224,7 @@ private void load(OverlayColourProvider colourProvider) Y = 100, Children = new Drawable[] { - new CatchObjectInspectorRuleset(rulesetInstance, playableBeatmap, modifiedMods!, (difficultyCalculator.Value as ExtendedCatchDifficultyCalculator)!, - processorBeatmap.Track.Rate) + new CatchObjectInspectorRuleset(rulesetInstance, playableBeatmap, modifiedMods!) { RelativeSizeAxes = Axes.Both, Clock = clock, diff --git a/PerformanceCalculatorGUI/Screens/ObjectInspection/OsuObjectInspectorRuleset.cs b/PerformanceCalculatorGUI/Screens/ObjectInspection/OsuObjectInspectorRuleset.cs index f975c0c949..081217cfd6 100644 --- a/PerformanceCalculatorGUI/Screens/ObjectInspection/OsuObjectInspectorRuleset.cs +++ b/PerformanceCalculatorGUI/Screens/ObjectInspection/OsuObjectInspectorRuleset.cs @@ -4,9 +4,11 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -20,15 +22,29 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection { public partial class OsuObjectInspectorRuleset : DrawableOsuEditorRuleset { - private readonly OsuDifficultyHitObject[] difficultyHitObjects; + private OsuDifficultyHitObject[] difficultyHitObjects = []; [Resolved] private ObjectDifficultyValuesContainer objectDifficultyValuesContainer { get; set; } = null!; - public OsuObjectInspectorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods, ExtendedOsuDifficultyCalculator difficultyCalculator, double clockRate) + [Resolved] + private Bindable difficultyCalculator { get; set; } = null!; + + public OsuObjectInspectorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { - difficultyHitObjects = difficultyCalculator.GetDifficultyHitObjects(beatmap, clockRate).Cast().ToArray(); + } + + protected override void LoadComplete() + { + var extendedDifficultyCalculator = (IExtendedDifficultyCalculator?)difficultyCalculator.Value; + + if (extendedDifficultyCalculator != null) + { + difficultyHitObjects = extendedDifficultyCalculator.GetDifficultyHitObjects().Cast().ToArray(); + } + + base.LoadComplete(); } protected override void Update() diff --git a/PerformanceCalculatorGUI/Screens/ObjectInspection/TaikoObjectInspectorRuleset.cs b/PerformanceCalculatorGUI/Screens/ObjectInspection/TaikoObjectInspectorRuleset.cs index b300ec58bd..bc4c89b061 100644 --- a/PerformanceCalculatorGUI/Screens/ObjectInspection/TaikoObjectInspectorRuleset.cs +++ b/PerformanceCalculatorGUI/Screens/ObjectInspection/TaikoObjectInspectorRuleset.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Edit; @@ -16,20 +18,32 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection { public partial class TaikoObjectInspectorRuleset : DrawableTaikoEditorRuleset { - private readonly TaikoDifficultyHitObject[] difficultyHitObjects; + private TaikoDifficultyHitObject[] difficultyHitObjects = []; [Resolved] private ObjectDifficultyValuesContainer objectDifficultyValuesContainer { get; set; } = null!; - public TaikoObjectInspectorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods, ExtendedTaikoDifficultyCalculator difficultyCalculator, double clockRate) + [Resolved] + private Bindable difficultyCalculator { get; set; } = null!; + + public TaikoObjectInspectorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { - difficultyHitObjects = difficultyCalculator.GetDifficultyHitObjects(beatmap, clockRate) - .Cast().ToArray(); - ShowSpeedChanges.Value = true; } + protected override void LoadComplete() + { + var extendedDifficultyCalculator = (IExtendedDifficultyCalculator?)difficultyCalculator.Value; + + if (extendedDifficultyCalculator != null) + { + difficultyHitObjects = extendedDifficultyCalculator.GetDifficultyHitObjects().Cast().ToArray(); + } + + base.LoadComplete(); + } + public override bool PropagatePositionalInputSubTree => false; public override bool PropagateNonPositionalInputSubTree => false; From 02969f18a435f95f5fda8dfe57ba89ac38ed436d Mon Sep 17 00:00:00 2001 From: StanR Date: Sat, 24 Jan 2026 17:18:25 +0500 Subject: [PATCH 2/3] Add per-object difficulty visualization to StrainVisualizer --- .../Screens/Simulate/StrainVisualizer.cs | 175 +++++++++++++----- .../Screens/SimulateScreen.cs | 39 +--- 2 files changed, 136 insertions(+), 78 deletions(-) diff --git a/PerformanceCalculatorGUI/Screens/Simulate/StrainVisualizer.cs b/PerformanceCalculatorGUI/Screens/Simulate/StrainVisualizer.cs index 209641bda9..40974c28bb 100644 --- a/PerformanceCalculatorGUI/Screens/Simulate/StrainVisualizer.cs +++ b/PerformanceCalculatorGUI/Screens/Simulate/StrainVisualizer.cs @@ -16,7 +16,9 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; @@ -27,8 +29,6 @@ namespace PerformanceCalculatorGUI.Screens.Simulate { public partial class StrainVisualizer : Container { - public readonly Bindable Skills = new Bindable([]); - private readonly List> graphToggles = new List>(); public readonly Bindable TimeUntilFirstStrain = new Bindable(); @@ -41,6 +41,11 @@ public partial class StrainVisualizer : Container [Resolved] private OverlayColourProvider? colourProvider { get; set; } + [Resolved] + private Bindable difficultyCalculator { get; set; } = null!; + + private const int strain_length = 400; + public StrainVisualizer() { RelativeSizeAxes = Axes.X; @@ -49,13 +54,15 @@ public StrainVisualizer() private float graphAlpha; - private void updateGraphs(ValueChangedEvent val) + private void updateGraphs(ValueChangedEvent val) { graphsContainer.Clear(); - var skills = val.NewValue.Where(x => x is StrainSkill or StrainDecaySkill).ToArray(); + if (val.NewValue is not IExtendedDifficultyCalculator extendedDifficultyCalculator) + return; + + var skills = extendedDifficultyCalculator.GetSkills(); - // dont bother if there are no strain skills to draw if (skills.Length == 0) { legendContainer.Clear(); @@ -68,7 +75,9 @@ private void updateGraphs(ValueChangedEvent val) addStrainBars(skills, strainLists); addTooltipBars(strainLists); - if (val.OldValue.Length == 0 || !val.NewValue.All(x => val.OldValue.Any(y => y.GetType().Name == x.GetType().Name))) + var oldSkills = (val.OldValue as IExtendedDifficultyCalculator)?.GetSkills(); + + if (oldSkills == null || oldSkills.Length == 0 || !skills.All(x => oldSkills.Any(y => y.GetType().Name == x.GetType().Name))) { // skill list changed - recreate toggles legendContainer.Clear(); @@ -183,15 +192,22 @@ private void load(OsuColour colours) } }); - Skills.BindValueChanged(updateGraphs); + difficultyCalculator.BindValueChanged(updateGraphs); } - private void addStrainBars(Skill[] skills, List strainLists) + private void addStrainBars(Skill[] skills, List strainLists) { - float strainMaxValue = strainLists.Max(list => list.Max()); + double strainMaxValue = strainLists.SelectMany(x => x).MaxBy(x => x.Difficulty)!.Difficulty; for (int i = 0; i < skills.Length; i++) { + var strainGraph = new StrainBarGraph + { + RelativeSizeAxes = Axes.Both, + MaxValue = (float)strainMaxValue + }; + strainGraph.CreateBars(strainLists[i]); + graphsContainer.AddRange(new Drawable[] { new BufferedContainer(cachedFrameBuffer: true) @@ -199,12 +215,7 @@ private void addStrainBars(Skill[] skills, List strainLists) RelativeSizeAxes = Axes.Both, Alpha = graphAlpha, Colour = skillColours[i % skillColours.Length], - Child = new StrainBarGraph - { - RelativeSizeAxes = Axes.Both, - MaxValue = strainMaxValue, - Values = strainLists[i] - } + Child = strainGraph } }); } @@ -216,9 +227,9 @@ private void addStrainBars(Skill[] skills, List strainLists) }); } - private void addTooltipBars(List strainLists, int nBars = 1000) + private void addTooltipBars(List strainLists, int nBars = 1000) { - double lastStrainTime = strainLists.Max(l => l.Length) * 400; + double lastStrainTime = strainLists.SelectMany(x => x).MaxBy(x => x.StartTime)!.StartTime; var tooltipList = new List(); @@ -244,23 +255,86 @@ private void addTooltipBars(List strainLists, int nBars = 1000) }); } - private static List getStrainLists(Skill[] skills) + private List getStrainLists(Skill[] skills) { - var strainLists = new List(); + var strainLists = new List(); foreach (var skill in skills) { - double[] strains = ((StrainSkill)skill).GetCurrentStrainPeaks().ToArray(); + if (skill is StrainSkill strainSkill) + { + double[] strains = strainSkill.GetCurrentStrainPeaks().ToArray(); - var skillStrainList = new List(); + var skillStrainList = new List(); - for (int i = 0; i < strains.Length; i++) - { - double strain = strains[i]; - skillStrainList.Add((float)strain); + for (int i = 0; i < strains.Length; i++) + { + double strain = strains[i]; + skillStrainList.Add(new Strain + { + Difficulty = strain, + StartTime = strain_length * i, // todo: use actual strain length + EndTime = (strain_length * i) + strain_length + }); + } + + strainLists.Add(skillStrainList.ToArray()); } + else + { + var difficultyObjects = (difficultyCalculator.Value as IExtendedDifficultyCalculator)!.GetDifficultyHitObjects(); + + var difficulties = skill.GetObjectDifficulties(); + + var skillStrainList = new List(); + + for (int i = 0; i < difficulties.Count - 1; i++) + { + double strain = difficulties[i]; + var difficultyObject = difficultyObjects[i]; + var nextDifficultyObject = i < difficulties.Count - 1 ? difficultyObjects[i + 1] : null; + + double startTime = difficultyObject.StartTime; + double endTime = difficultyObject.EndTime; + + if (nextDifficultyObject != null) + { + // cap length to object_length + strain_length to make map breaks display 0 difficulty instead of the last-object-before-break difficulty + endTime = Math.Min(endTime + strain_length, nextDifficultyObject.StartTime); + } + + skillStrainList.Add(new Strain + { + Difficulty = strain, + StartTime = startTime, + EndTime = endTime + }); + + // add blank bars between objects to make the graph consistent timescale-wise + if (nextDifficultyObject != null && nextDifficultyObject.StartTime - endTime > 0) + { + skillStrainList.Add(new Strain + { + Difficulty = 0, + StartTime = endTime, + EndTime = nextDifficultyObject.StartTime + }); + } + + // add blank strain_length bar in the end to make the object difficulties graph consistent with strain-based graphs + if (nextDifficultyObject == null) + { + skillStrainList.Add(new Strain + { + Difficulty = 0, + StartTime = endTime, + EndTime = endTime + strain_length + }); + } + } - strainLists.Add(skillStrainList.ToArray()); + strainLists.Add(skillStrainList.ToArray()); + } } return strainLists; @@ -274,33 +348,28 @@ public partial class StrainBarGraph : FillFlowContainer /// public float? MaxValue { get; set; } - /// - /// A list of floats that defines the length of each - /// - public IEnumerable Values + public void CreateBars(Strain[] values) { - set - { - Clear(); + Clear(); - foreach (float val in value) - { - float length = MaxValue ?? value.Max(); - if (length != 0) - length = val / length; + double maxLength = MaxValue ?? values.MaxBy(x => x.Difficulty)!.Difficulty; + double totalWidth = values.Sum(x => x.Length); - float size = value.Count(); - if (size != 0) - size = 1.0f / size; + foreach (Strain val in values) + { + double length = 0; + if (maxLength != 0) + length = val.Difficulty / maxLength; - Add(new Bar - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(size, 1), - Length = length, - Direction = BarDirection.BottomToTop - }); - } + float size = (float)(val.Length / totalWidth); + + Add(new Bar + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(size, 1), + Length = (float)length, + Direction = BarDirection.BottomToTop + }); } } } @@ -342,4 +411,12 @@ public IEnumerable Values } } } + + public class Strain + { + public double Difficulty { get; set; } + public double StartTime { get; set; } + public double EndTime { get; set; } + public double Length => EndTime - StartTime; + } } diff --git a/PerformanceCalculatorGUI/Screens/SimulateScreen.cs b/PerformanceCalculatorGUI/Screens/SimulateScreen.cs index bd6e24c9f1..e724e75163 100644 --- a/PerformanceCalculatorGUI/Screens/SimulateScreen.cs +++ b/PerformanceCalculatorGUI/Screens/SimulateScreen.cs @@ -26,7 +26,6 @@ using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; @@ -604,9 +603,6 @@ private void modsChanged(ValueChangedEvent> mods) updateMissesTextboxes(); - // recreate calculators to update DHOs - createCalculators(); - modSettingChangeTracker?.Dispose(); modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); modSettingChangeTracker.SettingChanged += m => @@ -614,7 +610,6 @@ private void modsChanged(ValueChangedEvent> mods) debouncedStatisticsUpdate?.Cancel(); debouncedStatisticsUpdate = Scheduler.AddDelayed(() => { - createCalculators(); updateMissesTextboxes(); calculateDifficulty(); calculatePerformance(); @@ -688,25 +683,21 @@ private void changeBeatmap(string beatmap) beatmapDataContainer.Show(); } - private void createCalculators() - { - if (working is null) - return; - - var rulesetInstance = ruleset.Value.CreateInstance(); - difficultyCalculator.Value = RulesetHelper.GetExtendedDifficultyCalculator(ruleset.Value, working); - performanceCalculator = rulesetInstance.CreatePerformanceCalculator(); - } - private void calculateDifficulty() { - if (working == null || difficultyCalculator.Value == null) + if (working == null) return; try { - difficultyAttributes = difficultyCalculator.Value.Calculate(appliedMods.Value); + var rulesetInstance = ruleset.Value.CreateInstance(); + var extendedDifficultyCalculator = RulesetHelper.GetExtendedDifficultyCalculator(ruleset.Value, working); + performanceCalculator = rulesetInstance.CreatePerformanceCalculator(); + + difficultyAttributes = extendedDifficultyCalculator.Calculate(appliedMods.Value); difficultyAttributesContainer.Attributes.Value = AttributeConversion.ToDictionary(difficultyAttributes); + + difficultyCalculator.Value = extendedDifficultyCalculator; } catch (Exception e) { @@ -715,16 +706,8 @@ private void calculateDifficulty() return; } - if (difficultyCalculator.Value is IExtendedDifficultyCalculator extendedDifficultyCalculator) - { - // StrainSkill always skips the first object - if (working.Beatmap?.HitObjects.Count > 1) - strainVisualizer.TimeUntilFirstStrain.Value = (int)working.Beatmap.HitObjects[1].StartTime; - - strainVisualizer.Skills.Value = extendedDifficultyCalculator.GetSkills(); - } - else - strainVisualizer.Skills.Value = Array.Empty(); + if (working.Beatmap?.HitObjects.Count > 1) + strainVisualizer.TimeUntilFirstStrain.Value = (int)working.Beatmap.HitObjects[1].StartTime; } private void debouncedCalculatePerformance() @@ -927,8 +910,6 @@ private void resetMods() private void resetCalculations() { - createCalculators(); - resetMods(); legacyTotalScore = null; From 832273925a9aaddf780ac846fd9a357feca2ba90 Mon Sep 17 00:00:00 2001 From: StanR Date: Sun, 25 Jan 2026 00:48:12 +0500 Subject: [PATCH 3/3] Split `getStrainLists` --- .../Screens/Simulate/StrainVisualizer.cs | 132 ++++++++++-------- 1 file changed, 72 insertions(+), 60 deletions(-) diff --git a/PerformanceCalculatorGUI/Screens/Simulate/StrainVisualizer.cs b/PerformanceCalculatorGUI/Screens/Simulate/StrainVisualizer.cs index 40974c28bb..80b4a76f69 100644 --- a/PerformanceCalculatorGUI/Screens/Simulate/StrainVisualizer.cs +++ b/PerformanceCalculatorGUI/Screens/Simulate/StrainVisualizer.cs @@ -261,83 +261,95 @@ private List getStrainLists(Skill[] skills) foreach (var skill in skills) { - if (skill is StrainSkill strainSkill) + switch (skill) { - double[] strains = strainSkill.GetCurrentStrainPeaks().ToArray(); + case StrainSkill strainSkill: + strainLists.Add(getStrainSkillStrainList(strainSkill)); + break; - var skillStrainList = new List(); + default: + strainLists.Add(getStrainList(skill)); + break; + } + } - for (int i = 0; i < strains.Length; i++) - { - double strain = strains[i]; - skillStrainList.Add(new Strain - { - Difficulty = strain, - StartTime = strain_length * i, // todo: use actual strain length - EndTime = (strain_length * i) + strain_length - }); - } + return strainLists; + } - strainLists.Add(skillStrainList.ToArray()); - } - else + private Strain[] getStrainSkillStrainList(StrainSkill strainSkill) + { + double[] strains = strainSkill.GetCurrentStrainPeaks().ToArray(); + + var skillStrainList = new List(); + + for (int i = 0; i < strains.Length; i++) + { + double strain = strains[i]; + skillStrainList.Add(new Strain { - var difficultyObjects = (difficultyCalculator.Value as IExtendedDifficultyCalculator)!.GetDifficultyHitObjects(); + Difficulty = strain, + StartTime = strain_length * i, // todo: use actual strain length + EndTime = (strain_length * i) + strain_length + }); + } - var difficulties = skill.GetObjectDifficulties(); + return skillStrainList.ToArray(); + } - var skillStrainList = new List(); + private Strain[] getStrainList(Skill skill) + { + var difficultyObjects = (difficultyCalculator.Value as IExtendedDifficultyCalculator)!.GetDifficultyHitObjects(); - for (int i = 0; i < difficulties.Count - 1; i++) - { - double strain = difficulties[i]; - var difficultyObject = difficultyObjects[i]; - var nextDifficultyObject = i < difficulties.Count - 1 ? difficultyObjects[i + 1] : null; + var difficulties = skill.GetObjectDifficulties(); - double startTime = difficultyObject.StartTime; - double endTime = difficultyObject.EndTime; + var skillStrainList = new List(); - if (nextDifficultyObject != null) - { - // cap length to object_length + strain_length to make map breaks display 0 difficulty instead of the last-object-before-break difficulty - endTime = Math.Min(endTime + strain_length, nextDifficultyObject.StartTime); - } + for (int i = 0; i < difficulties.Count - 1; i++) + { + double strain = difficulties[i]; + var difficultyObject = difficultyObjects[i]; + var nextDifficultyObject = i < difficulties.Count - 1 ? difficultyObjects[i + 1] : null; - skillStrainList.Add(new Strain - { - Difficulty = strain, - StartTime = startTime, - EndTime = endTime - }); + double startTime = difficultyObject.StartTime; + double endTime = difficultyObject.EndTime; - // add blank bars between objects to make the graph consistent timescale-wise - if (nextDifficultyObject != null && nextDifficultyObject.StartTime - endTime > 0) - { - skillStrainList.Add(new Strain - { - Difficulty = 0, - StartTime = endTime, - EndTime = nextDifficultyObject.StartTime - }); - } + if (nextDifficultyObject != null) + { + // cap length to object_length + strain_length to make map breaks display 0 difficulty instead of the last-object-before-break difficulty + endTime = Math.Min(endTime + strain_length, nextDifficultyObject.StartTime); + } - // add blank strain_length bar in the end to make the object difficulties graph consistent with strain-based graphs - if (nextDifficultyObject == null) - { - skillStrainList.Add(new Strain - { - Difficulty = 0, - StartTime = endTime, - EndTime = endTime + strain_length - }); - } - } + skillStrainList.Add(new Strain + { + Difficulty = strain, + StartTime = startTime, + EndTime = endTime + }); + + // add blank bars between objects to make the graph consistent timescale-wise + if (nextDifficultyObject != null && nextDifficultyObject.StartTime - endTime > 0) + { + skillStrainList.Add(new Strain + { + Difficulty = 0, + StartTime = endTime, + EndTime = nextDifficultyObject.StartTime + }); + } - strainLists.Add(skillStrainList.ToArray()); + // add blank strain_length bar in the end to make the object difficulties graph consistent with strain-based graphs + if (nextDifficultyObject == null) + { + skillStrainList.Add(new Strain + { + Difficulty = 0, + StartTime = endTime, + EndTime = endTime + strain_length + }); } } - return strainLists; + return skillStrainList.ToArray(); } }