From 121199b186028baa6fc1b639a361bd1174940966 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 25 Sep 2024 13:01:17 +0300 Subject: [PATCH 1/7] initial commit --- .../Difficulty/Skills/OsuStrainSkill.cs | 22 +++-- .../Rulesets/Difficulty/Skills/StrainSkill.cs | 85 +++++++++++++++++-- 2 files changed, 92 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 6823512cef12..cc5629a486de 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; -using System.Linq; using osu.Framework.Utils; namespace osu.Game.Rulesets.Osu.Difficulty.Skills @@ -33,22 +32,27 @@ public override double DifficultyValue() double difficulty = 0; double weight = 1; - // Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871). - // These sections will not contribute to the difficulty. - var peaks = GetCurrentStrainPeaks().Where(p => p > 0); + List strains = GetCurrentStrainsSorted(); - List strains = peaks.OrderDescending().ToList(); + int reducedSectionCount = Math.Min(strains.Count, ReducedSectionCount); + double[] reducedStrains = new double[reducedSectionCount]; // We are reducing the highest strains first to account for extreme difficulty spikes - for (int i = 0; i < Math.Min(strains.Count, ReducedSectionCount); i++) + for (int i = 0; i < reducedSectionCount; i++) { double scale = Math.Log10(Interpolation.Lerp(1, 10, Math.Clamp((float)i / ReducedSectionCount, 0, 1))); - strains[i] *= Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale); + reducedStrains[i] = strains[i] * Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale); } + // Remove reduced strains as they are no longer sorted + strains.RemoveRange(0, reducedSectionCount); + + // Insert them back + foreach (double reducedStrain in reducedStrains) + InsertElementInReverseSortedList(strains, reducedStrain); + // Difficulty is the weighted sum of the highest strains from every section. - // We're sorting from highest to lowest strain. - foreach (double strain in strains.OrderDescending()) + foreach (double strain in strains) { difficulty += strain * weight; weight *= DecayWeight; diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index b07e8399c024..2dc594068a21 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -55,9 +55,17 @@ public sealed override void Process(DifficultyHitObject current) saveCurrentPeak(); startNewSectionFrom(currentSectionEnd, current); currentSectionEnd += SectionLength; + + amountOfStrainsAddedSinceSave++; } - currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak); + double currentStrain = StrainValueAt(current); + + if (currentSectionPeak < currentStrain) + { + currentSectionPeak = currentStrain; + isSavedCurrentStrainRelevant = false; + } } /// @@ -102,13 +110,9 @@ public override double DifficultyValue() double difficulty = 0; double weight = 1; - // Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871). - // These sections will not contribute to the difficulty. - var peaks = GetCurrentStrainPeaks().Where(p => p > 0); - // Difficulty is the weighted sum of the highest strains from every section. // We're sorting from highest to lowest strain. - foreach (double strain in peaks.OrderDescending()) + foreach (double strain in GetCurrentStrainsSorted()) { difficulty += strain * weight; weight *= DecayWeight; @@ -116,5 +120,74 @@ public override double DifficultyValue() return difficulty; } + + protected List GetCurrentStrainsSorted() + { + List strains; + + // If no saved strains - calculate them from 0, and save them after that + if (savedSortedStrains == null || savedSortedStrains.Count == 0) + { + var peaks = GetCurrentStrainPeaks().Where(p => p > 0); + + strains = peaks.OrderDescending().ToList(); + + savedSortedStrains = new List(strains); + amountOfStrainsAddedSinceSave = 0; + savedCurrentStrain = currentSectionPeak; + isSavedCurrentStrainRelevant = true; + } + // If several sections were added since last save - insert them into saved strains list + else if (amountOfStrainsAddedSinceSave > 0) + { + var newPeaks = GetCurrentStrainPeaks().TakeLast(amountOfStrainsAddedSinceSave).Where(p => p > 0); + foreach (double newPeak in newPeaks) + InsertElementInReverseSortedList(savedSortedStrains, newPeak); + + strains = new List(savedSortedStrains); + + amountOfStrainsAddedSinceSave = 0; + savedCurrentStrain = currentSectionPeak; + isSavedCurrentStrainRelevant = true; + } + // If no section was added, but last one was changed - find it and replace it with new one + else if (!isSavedCurrentStrainRelevant && savedCurrentStrain > 0) + { + int invalidStrainIndex = savedSortedStrains.BinarySearch(savedCurrentStrain, new ReverseComparer()); + savedSortedStrains.RemoveAt(invalidStrainIndex); + InsertElementInReverseSortedList(savedSortedStrains, currentSectionPeak); + + strains = new List(savedSortedStrains); + + savedCurrentStrain = currentSectionPeak; + isSavedCurrentStrainRelevant = true; + } + // Otherwise - just use saved strains + else + { + strains = new List(savedSortedStrains); + } + + return strains; + } + + private List? savedSortedStrains; + private double savedCurrentStrain; + private bool isSavedCurrentStrainRelevant; + private int amountOfStrainsAddedSinceSave; + + protected static void InsertElementInReverseSortedList(List list, double element) + { + int indexToInsert = list.BinarySearch(element, new ReverseComparer()); + if (indexToInsert < 0) + indexToInsert = ~indexToInsert; + + list.Insert(indexToInsert, element); + } + + private class ReverseComparer : IComparer + { + public int Compare(double x, double y) => Comparer.Default.Compare(y, x); + } } } From d8c9d5630749809bf127d9b9216d4a43cdaaf717 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 12 Oct 2024 18:29:40 +0300 Subject: [PATCH 2/7] changed to much simpler version --- .../Difficulty/Skills/OsuStrainSkill.cs | 7 +- .../Rulesets/Difficulty/Skills/StrainSkill.cs | 86 +++++-------------- 2 files changed, 25 insertions(+), 68 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index cc5629a486de..0bdd3e3f30a4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -2,10 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Framework.Utils; +using osu.Framework.Lists; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -32,7 +32,7 @@ public override double DifficultyValue() double difficulty = 0; double weight = 1; - List strains = GetCurrentStrainsSorted(); + SortedList strains = GetCurrentStrainsSorted(); int reducedSectionCount = Math.Min(strains.Count, ReducedSectionCount); double[] reducedStrains = new double[reducedSectionCount]; @@ -48,8 +48,7 @@ public override double DifficultyValue() strains.RemoveRange(0, reducedSectionCount); // Insert them back - foreach (double reducedStrain in reducedStrains) - InsertElementInReverseSortedList(strains, reducedStrain); + strains.AddRange(reducedStrains); // Difficulty is the weighted sum of the highest strains from every section. foreach (double strain in strains) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 2dc594068a21..c199ba1d0b3d 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Lists; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; @@ -55,17 +56,12 @@ public sealed override void Process(DifficultyHitObject current) saveCurrentPeak(); startNewSectionFrom(currentSectionEnd, current); currentSectionEnd += SectionLength; - - amountOfStrainsAddedSinceSave++; } double currentStrain = StrainValueAt(current); if (currentSectionPeak < currentStrain) - { currentSectionPeak = currentStrain; - isSavedCurrentStrainRelevant = false; - } } /// @@ -121,73 +117,35 @@ public override double DifficultyValue() return difficulty; } - protected List GetCurrentStrainsSorted() - { - List strains; - - // If no saved strains - calculate them from 0, and save them after that - if (savedSortedStrains == null || savedSortedStrains.Count == 0) - { - var peaks = GetCurrentStrainPeaks().Where(p => p > 0); - - strains = peaks.OrderDescending().ToList(); - - savedSortedStrains = new List(strains); - amountOfStrainsAddedSinceSave = 0; - savedCurrentStrain = currentSectionPeak; - isSavedCurrentStrainRelevant = true; - } - // If several sections were added since last save - insert them into saved strains list - else if (amountOfStrainsAddedSinceSave > 0) - { - var newPeaks = GetCurrentStrainPeaks().TakeLast(amountOfStrainsAddedSinceSave).Where(p => p > 0); - foreach (double newPeak in newPeaks) - InsertElementInReverseSortedList(savedSortedStrains, newPeak); - - strains = new List(savedSortedStrains); + /// + /// Amount of strains that will be saved in the sorted strains list. + /// Use value = 0 in case you want all strains to be saved. + /// + protected virtual int MaxStrainCount => 200; - amountOfStrainsAddedSinceSave = 0; - savedCurrentStrain = currentSectionPeak; - isSavedCurrentStrainRelevant = true; - } - // If no section was added, but last one was changed - find it and replace it with new one - else if (!isSavedCurrentStrainRelevant && savedCurrentStrain > 0) - { - int invalidStrainIndex = savedSortedStrains.BinarySearch(savedCurrentStrain, new ReverseComparer()); - savedSortedStrains.RemoveAt(invalidStrainIndex); - InsertElementInReverseSortedList(savedSortedStrains, currentSectionPeak); + protected SortedList GetCurrentStrainsSorted() + { + int newStrainsToAdd = strainPeaks.Count - amountOfStrainsAddedSinceSave; - strains = new List(savedSortedStrains); + var peaks = strainPeaks.TakeLast(newStrainsToAdd); + savedSortedStrains.AddRange(peaks); + amountOfStrainsAddedSinceSave = strainPeaks.Count; - savedCurrentStrain = currentSectionPeak; - isSavedCurrentStrainRelevant = true; - } - // Otherwise - just use saved strains - else + // We're saving only the largest 200 strains + int excessStrainsCount = savedSortedStrains.Count - MaxStrainCount; + if (MaxStrainCount > 0 && excessStrainsCount > 0) { - strains = new List(savedSortedStrains); + savedSortedStrains.RemoveRange(savedSortedStrains.Count - excessStrainsCount, excessStrainsCount); } - return strains; - } + var strainsWithCurrent = new SortedList((double a, double b) => { return a < b ? 1 : (a > b ? -1 : 0); }); + strainsWithCurrent.AddRange(savedSortedStrains); + strainsWithCurrent.Add(currentSectionPeak); - private List? savedSortedStrains; - private double savedCurrentStrain; - private bool isSavedCurrentStrainRelevant; - private int amountOfStrainsAddedSinceSave; - - protected static void InsertElementInReverseSortedList(List list, double element) - { - int indexToInsert = list.BinarySearch(element, new ReverseComparer()); - if (indexToInsert < 0) - indexToInsert = ~indexToInsert; - - list.Insert(indexToInsert, element); + return strainsWithCurrent; } - private class ReverseComparer : IComparer - { - public int Compare(double x, double y) => Comparer.Default.Compare(y, x); - } + private SortedList savedSortedStrains = new SortedList((double a, double b) => { return a < b ? 1 : (a > b ? -1 : 0); }); + private int amountOfStrainsAddedSinceSave = 0; } } From b23d02de5bcf2846a83bf5adcc26210c495e1d4b Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 12 Oct 2024 18:37:55 +0300 Subject: [PATCH 3/7] revert unnecessary change --- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index c199ba1d0b3d..493e4f7fafa0 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -58,10 +58,7 @@ public sealed override void Process(DifficultyHitObject current) currentSectionEnd += SectionLength; } - double currentStrain = StrainValueAt(current); - - if (currentSectionPeak < currentStrain) - currentSectionPeak = currentStrain; + currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak); } /// From 57f51ac8ea18686181fd6d22c86793fdb0a03317 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 12 Oct 2024 18:38:03 +0300 Subject: [PATCH 4/7] add check for 0 strains --- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 493e4f7fafa0..32d09b08e351 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -124,7 +124,7 @@ protected SortedList GetCurrentStrainsSorted() { int newStrainsToAdd = strainPeaks.Count - amountOfStrainsAddedSinceSave; - var peaks = strainPeaks.TakeLast(newStrainsToAdd); + var peaks = strainPeaks.TakeLast(newStrainsToAdd).Where(s => s > 0); savedSortedStrains.AddRange(peaks); amountOfStrainsAddedSinceSave = strainPeaks.Count; From b4ab65cdc5b622160443cbd979fd48a2d3afebe2 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 12 Oct 2024 18:41:38 +0300 Subject: [PATCH 5/7] fixed CI? no idea why it's complaining about no List included --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 0bdd3e3f30a4..3b605bd1cab8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Framework.Utils; From 0b8cd01debe0c711169d45c17d0afc58a1319910 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 12 Oct 2024 18:47:48 +0300 Subject: [PATCH 6/7] fixed CI --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index f165ca7824fa..6aedc95da6d8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Collections.Generic; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; From af79962c08c4f13123229d54913b734f62f3ef87 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 12 Oct 2024 19:08:20 +0300 Subject: [PATCH 7/7] fixed CI (again) --- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 32d09b08e351..7f7496106872 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -130,19 +130,20 @@ protected SortedList GetCurrentStrainsSorted() // We're saving only the largest 200 strains int excessStrainsCount = savedSortedStrains.Count - MaxStrainCount; + if (MaxStrainCount > 0 && excessStrainsCount > 0) { savedSortedStrains.RemoveRange(savedSortedStrains.Count - excessStrainsCount, excessStrainsCount); } - var strainsWithCurrent = new SortedList((double a, double b) => { return a < b ? 1 : (a > b ? -1 : 0); }); + var strainsWithCurrent = new SortedList((a, b) => a < b ? 1 : (a > b ? -1 : 0)); strainsWithCurrent.AddRange(savedSortedStrains); strainsWithCurrent.Add(currentSectionPeak); return strainsWithCurrent; } - private SortedList savedSortedStrains = new SortedList((double a, double b) => { return a < b ? 1 : (a > b ? -1 : 0); }); - private int amountOfStrainsAddedSinceSave = 0; + private readonly SortedList savedSortedStrains = new SortedList((a, b) => a < b ? 1 : (a > b ? -1 : 0)); + private int amountOfStrainsAddedSinceSave; } }