From 01585027b1f869a4e1c26389c569ad4bfed74ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Nemes?= Date: Tue, 6 Jun 2017 15:39:37 +0200 Subject: [PATCH] OsuDifficultyBeatmap enumeration logic made clearer, more documentation added. --- .../OsuDifficulty/OsuDifficultyCalculator.cs | 2 +- .../Preprocessing/OsuDifficultyBeatmap.cs | 43 +++++++++++-------- .../Preprocessing/OsuDifficultyHitObject.cs | 17 ++++++-- .../OsuDifficulty/Skills/Aim.cs | 2 +- .../OsuDifficulty/Skills/Skill.cs | 20 ++++++++- .../OsuDifficulty/Skills/Speed.cs | 4 +- .../OsuDifficulty/Utils/History.cs | 40 +++++++++++------ 7 files changed, 87 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs index d8d48f8734..a164566263 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty protected override double CalculateInternal(Dictionary categoryDifficulty) { OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Objects); - Skill[] skills = new Skill[] + Skill[] skills = { new Aim(), new Speed() diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs index c2446409c5..a96dd31078 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs @@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing private readonly IEnumerator difficultyObjects; private readonly Queue onScreen = new Queue(); + /// + /// Creates an enumerable, which handles the preprocessing of HitObjects, getting them ready to be used in difficulty calculation. + /// public OsuDifficultyBeatmap(List objects) { // Sort HitObjects by StartTime - they are not correctly ordered in some cases. @@ -20,38 +23,40 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing difficultyObjects = createDifficultyObjectEnumerator(objects); } + /// + /// Returns an enumerator that enumerates all difficulty objects in the beatmap. + /// The inner loop adds notes that appear on screen into a queue until we need to hit the next note, + /// the outer loop returns notes from this queue one at a time, only after they had to be hit, and should no longer be on screen. + /// This means that we can loop through every object that is on screen at the time when a new one appears, + /// allowing us to determine a reading strain for the note that just appeared. + /// public IEnumerator GetEnumerator() { - do + while (true) { // Add upcoming notes to the queue until we have at least one note that had been hit and can be dequeued. // This means there is always at least one note in the queue unless we reached the end of the map. - bool hasNext; do { - hasNext = difficultyObjects.MoveNext(); - if (onScreen.Count == 0 && !hasNext) - yield break; // Stop if we have an empty enumerator. + if (!difficultyObjects.MoveNext()) + break; // New notes can't be added anymore, but we still need to dequeue and return the ones already on screen. - if (hasNext) + OsuDifficultyHitObject latest = difficultyObjects.Current; + // Calculate flow values here + + foreach (OsuDifficultyHitObject h in onScreen) { - OsuDifficultyHitObject latest = difficultyObjects.Current; - // Calculate flow values here - - foreach (OsuDifficultyHitObject h in onScreen) - { - h.MsUntilHit -= latest.Ms; - // Calculate reading strain here - } - - onScreen.Enqueue(latest); + h.TimeUntilHit -= latest.DeltaTime; + // Calculate reading strain here } - } - while (onScreen.Peek().MsUntilHit > 0 && hasNext); // Keep adding new notes on screen while there is still time before we have to hit the next one. + onScreen.Enqueue(latest); + } + while (onScreen.Peek().TimeUntilHit > 0); // Keep adding new notes on screen while there is still time before we have to hit the next one. + + if (onScreen.Count == 0) break; // We have reached the end of the map and enumerated all the notes. yield return onScreen.Dequeue(); // Remove and return notes one by one that had to be hit before the latest note appeared. } - while (onScreen.Count > 0); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs index 4497f96c89..a83ae6ee7e 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -8,6 +8,9 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing { public class OsuDifficultyHitObject { + /// + /// The current note. Attributes are calculated relative to previous ones. + /// public OsuHitObject BaseObject { get; } /// @@ -18,14 +21,20 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing /// /// Milliseconds elapsed since the StartTime of the previous note. /// - public double Ms { get; private set; } + public double DeltaTime { get; private set; } - public double MsUntilHit { get; set; } + /// + /// Number of milliseconds until the note has to be hit. + /// + public double TimeUntilHit { get; set; } private const int normalized_radius = 52; private readonly OsuHitObject[] t; + /// + /// Constructs a wrapper around a HitObject calculating additional data required for difficulty calculation. + /// public OsuDifficultyHitObject(OsuHitObject[] triangle) { t = triangle; @@ -51,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing private void setTimingValues() { // Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure. - Ms = Math.Max(40, t[0].StartTime - t[1].StartTime); - MsUntilHit = 450; // BaseObject.PreEmpt; + DeltaTime = Math.Max(40, t[0].StartTime - t[1].StartTime); + TimeUntilHit = 450; // BaseObject.PreEmpt; } } } diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs index 57fcff965a..e17679161b 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills protected override double SkillMultiplier => 26.25; protected override double StrainDecayBase => 0.15; - protected override double StrainValue() => Math.Pow(Current.Distance, 0.99) / Current.Ms; + protected override double StrainValue() => Math.Pow(Current.Distance, 0.99) / Current.DeltaTime; } } diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs index c2aa55d650..498374df8d 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs @@ -11,10 +11,25 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills { public abstract class Skill { + /// + /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other. + /// protected abstract double SkillMultiplier { get; } + + /// + /// Determines how quickly strain decays for the given skill. + /// For example a value of 0.15 indicates that strain decays to 15% of it's original value in one second. + /// protected abstract double StrainDecayBase { get; } + /// + /// The note that will be processed. + /// protected OsuDifficultyHitObject Current; + + /// + /// Notes that were processed previously. They can affect the strain value of the current note. + /// protected History Previous = new History(2); // Contained objects not used yet private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap. @@ -28,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills { Current = h; - currentStrain *= strainDecay(Current.Ms); + currentStrain *= strainDecay(Current.DeltaTime); if (!(Current.BaseObject is Spinner)) currentStrain += StrainValue() * SkillMultiplier; @@ -78,6 +93,9 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills return difficulty; } + /// + /// Calculates the strain value of the current note. This value is affected by previous notes. + /// protected abstract double StrainValue(); private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000); diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs index 33e8fec829..934df4dd8a 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills else speedValue = 0.95; - return speedValue / Current.Ms; + return speedValue / Current.DeltaTime; } } -} \ No newline at end of file +} diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs index 38727707d9..f1f7b189be 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils { /// - /// An indexed stack with Push() only, which disposes items at the bottom once the size limit has been reached. + /// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full. /// Indexing starts at the top of the stack. /// public class History : IEnumerable @@ -16,17 +16,28 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils public int Count { get; private set; } private readonly T[] array; - private readonly int size; + private readonly int capacity; private int marker; // Marks the position of the most recently added item. - public History(int size) + // + /// + /// Initializes a new instance of the History class that is empty and has the specified capacity. + /// + /// The number of items the History can hold. + public History(int capacity) { - this.size = size; - array = new T[size]; - marker = size; // Set marker to the end of the array, outside of the indexed range by one. + if (capacity < 0) + throw new ArgumentOutOfRangeException(); + + this.capacity = capacity; + array = new T[capacity]; + marker = capacity; // Set marker to the end of the array, outside of the indexed range by one. } - public T this[int i] // Index 0 returns the most recently added item. + /// + /// The most recently added item is returned at index 0. + /// + public T this[int i] { get { @@ -34,8 +45,8 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils throw new IndexOutOfRangeException(); i += marker; - if (i > size - 1) - i -= size; + if (i > capacity - 1) + i -= capacity; return array[i]; } @@ -48,22 +59,25 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils public void Push(T item) // Overwrite the oldest item instead of shifting every item by one with every addition. { if (marker == 0) - marker = size - 1; + marker = capacity - 1; else --marker; array[marker] = item; - if (Count < size) + if (Count < capacity) ++Count; } + /// + /// Returns an enumerator which enumerates items in the history starting from the most recently added item. + /// public IEnumerator GetEnumerator() { - for (int i = marker; i < size; ++i) + for (int i = marker; i < capacity; ++i) yield return array[i]; - if (Count == size) + if (Count == capacity) for (int i = 0; i < marker; ++i) yield return array[i]; }