Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor of osu!taiko difficulty calculation code #31636

Open
wants to merge 17 commits into
base: pp-dev
Choose a base branch
from

Conversation

tsunyoku
Copy link
Member

General refactor of some of the recent osu!taiko changes to be a bit more maintainable and less weird.

@tsunyoku
Copy link
Member Author

!diffcalc
RULESET=taiko
OSU_A=https://github.com/ppy/osu/tree/pp-dev
OSU_B=#31636

Copy link

@tsunyoku
Copy link
Member Author

oof, something is wrong

@tsunyoku tsunyoku marked this pull request as draft January 23, 2025 11:32
@tsunyoku tsunyoku marked this pull request as ready for review January 26, 2025 16:21
@tsunyoku
Copy link
Member Author

!diffcalc
RULESET=taiko
OSU_A=https://github.com/ppy/osu/tree/pp-dev
OSU_B=#31636

Copy link

@tsunyoku
Copy link
Member Author

!diffcalc
RULESET=taiko
OSU_A=https://github.com/ppy/osu/tree/pp-dev
OSU_B=#31636

Copy link

Copy link
Member

@Lawtrohux Lawtrohux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10/10. so much cleaner then my structure, will be sure to follow it from now on.

@@ -111,6 +117,27 @@ public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitOb
NoteIndex = noteObjects.Count;
noteObjects.Add(this);
}

// Using `hitObject.StartTime` causes floating point error differences
double normalizedStartTime = StartTime * clockRate;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for consistency we use non-american everywhere else

Comment on lines 10 to 12
/// <summary>
/// Represents a group of <see cref="TaikoDifficultyHitObject"/>s with no rhythm variation.
/// </summary>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a sequential grouping of hitobjects that share the same rhythm? Would probably mention that for the reader.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is.

Represents a group of sequential <see cref="TaikoDifficultyHitObject"/>s with no rhythm variation.

/// </summary>
public class SameRhythmGroupedHitObjects : IHasInterval
{
public List<TaikoDifficultyHitObject> Children { get; private set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would probably name this "HitObjects" or "Objects".

This, along with the class renaming comment above, apply to the other class as well. Over there I think "Groups" makes more sense than "Children".


public TaikoDifficultyHitObject FirstHitObject => Children[0];

public SameRhythmGroupedHitObjects? Previous;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Publicly settable? Also for a few of the fields below.


/// <summary>
/// The interval in ms of each hit object in this <see cref="SameRhythmGroupedHitObjects"/>. This is only defined if there is
/// more than two hit objects in this <see cref="SameRhythmGroupedHitObjects"/>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just me but I find it weird that this is only valid for >2 objects yet the class itself is used when there's ">1" hitobjects.

This code in particular breaks my brain:

double intervalDifficulty = ratioDifficulty(sameRhythmGroupedHitObjects.HitObjectIntervalRatio);
double? previousInterval = sameRhythmGroupedHitObjects.Previous?.HitObjectInterval;
intervalDifficulty *= repeatedIntervalPenalty(sameRhythmGroupedHitObjects, hitWindow);
// If a previous interval exists and there are multiple hit objects in the sequence:
if (previousInterval != null && sameRhythmGroupedHitObjects.Children.Count > 1)
{
double expectedDurationFromPrevious = (double)previousInterval * sameRhythmGroupedHitObjects.Children.Count;
double durationDifference = sameRhythmGroupedHitObjects.Duration - expectedDurationFromPrevious;

Reading as "if the previous group has >2 objects and the current group has >1 object, do this thing". Perhaps there needs to be a more informative bool-returning method along the lines of "isvalid" to denote the valid use-cases of this class...

My original comment is that I would expect this to be valid for two hitobjects, and to be 0 for one hitobject.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sameRhythmGroupedHitObjects.Children.Count > 1 ensures the current group (with the previous having an interval has more than one object to ensure there's an interval to compare, instead of a single object, I'll agree that my comment is quite confusing however.

If a previous interval exists, and there is a current interval to compare

/// <summary>
/// Represents a group of <see cref="TaikoDifficultyHitObject"/>s with no rhythm variation.
/// </summary>
public class SameRhythmGroupedHitObjects : IHasInterval
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would probably prefer something more along the lines of SameRhythmHitObjectGrouping than a pluralised class containing a plural of objects.

namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
{
/// <summary>
/// Represents <see cref="SameRhythmGroupedHitObjects"/> grouped by their <see cref="SameRhythmGroupedHitObjects.StartTime"/>'s interval.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This xmldoc doesn't do much for me without reading into how it works. Can it be explained in less code-y terms?

For example, does every grouping in this class have the same rhythm?

Copy link
Member

@Lawtrohux Lawtrohux Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It represents a group of hit objects grouped by the same spacing between their start times (interval). This then is calculated as a singular rhythmic group or segment. Every grouping in this class will be grouped by having the same interval, with the changes garnering the difficulty between them.


namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm
{
public static class TaikoRhythmDifficultyPreprocessor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is way too much to understand for me without any comments. How are we grouping things? What is a "pattern" grouping and how does that differ from a "rhythm" grouping?

i.e. if we have the objects aa b b b aa b b b do we create 4 "rhythm" groupings (aa, b b b, aa, b b b) and one "pattern" grouping containing all 4 of those? Or maybe 2 "pattern" groupings as aa b b b?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be grouped as the first example. Can admit my lack of comments in this isn't ideal.

Rhythm grouping follows the individual note intervals - putting the hit objects intervals when they match, essentially objects that share a basic spacing in time.

Pattern grouping is the parent class of this, it looks at the groups of notes within the rhythm groups, and put them together when the repetition of intervals is consistently the same across multiple sections of maps. The first matches the time gap between individual notes, whereas the parent compares the whole patterns of intervals among the groups of notes.

Below would be more inline comment friendly.

Rhythm grouping = Groups of individual notes that share the same interval.
Pattern grouping = Groups of rhythm groups that share repeating interval changes.

@smoogipoo smoogipoo requested a review from peppy January 27, 2025 12:39
@smoogipoo
Copy link
Contributor

Requesting review from @peppy because you reviewed the taiko changes in the primary PR.

@tsunyoku
Copy link
Member Author

Looping in @Lawtrohux to address some of these review comments as a lot of the things commented stand prior to my refactor so I think he's better tasked at explaining some of these workings than I am.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

3 participants