Skip to content

Conversation

@frenzibyte
Copy link
Member

@frenzibyte frenzibyte commented Aug 7, 2025

RFC. This does not cover online scores not present in the database. Implementation is done as discussed, using a dictionary to achieve O(n) time complexity, as proposed by @peppy.

Performance is tested through TestSceneSongSelectGrouping.TestPerformance, which loads a total of 10k difficulties and 1.2k scores to a temporary realm database (going any higher takes too long to load). I've also confirmed the generated databases are in a temporary folder (/var/folders/...) since the database grows up to 7MB to contain all of the imported models.

Code-wise this PR violates rules to achieve the end result, by including realm directly in the grouping class. I think we're at the point where it doesn't really matter if realm lookups are done directly inside the grouping class or encapsulated by a delegate, and if delegates are still favoured then we'll need a delegate which wraps realm.Run, and the signature for that won't be pretty (I proposed something similar for collection grouping before and it was faced with disgust).

I've also took the time to tidy up TestSceneSongSelectGrouping as it keeps containing more realm/API-based tests.

@peppy
Copy link
Member

peppy commented Aug 7, 2025

(going any higher takes too long to load)

pretty scary since users will hit this number in no time

@frenzibyte
Copy link
Member Author

frenzibyte commented Aug 7, 2025

Sorry, I meant it will take too long for the test to import all those beatmaps and scores. I've still waited patiently and tested 12k scores, it had almost no effect on the filter time.

@peppy peppy self-requested a review August 7, 2025 10:14
@peppy peppy force-pushed the rank-achieved-grouping branch from 8d1ae6e to c23856c Compare August 7, 2025 10:16
#region Rank Achieved grouping

[Test]
public void TestRankAchievedGrouping()
Copy link
Member

Choose a reason for hiding this comment

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

I'm not too sure why tests have started to be added to this class and not the non-test-scene one. Did you run into an issue which meant you couldn't put them there..?

It's also literally the only reason the realm instance is provided as nullable. I don't like making arguments nullable just for tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've set up this test scene to take away the complications of setting up a local temporary database and API access, I've also stated that in the xmldoc of the test scene class.

This test scene also helps confirming that the grouping filter is correctly hooked up with those components (especially cases like a user logging out or collections being modified, which isn't necessarily identified by the grouping filter class itself, but rather signaled by FilterControl / components of the screen).


public bool Equals(IBeatmapInfo? other) => other is BeatmapInfo b && Equals(b);

public override int GetHashCode()
Copy link
Member

@peppy peppy Aug 7, 2025

Choose a reason for hiding this comment

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

What is this..? Reading on I can see why this might have been added, but I do not think it's a good idea.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've went with this approach following BeatmapSetInfo and the SetItems dictionary set up in the grouping filter, which uses BeatmapSetInfo as a key.

Copy link
Member

Choose a reason for hiding this comment

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

That dictionary is a bit different because usages need to read the BeatmapSetInfo content. That's not the case for this private usage so let's not. It's just extra complexity to think about. I see a class being used as a dinctionary key and my brain goes into panic.

new BeatmapCarouselFilterMatching(() => Criteria!),
new BeatmapCarouselFilterSorting(() => Criteria!),
grouping = new BeatmapCarouselFilterGrouping(() => Criteria!, () => detachedCollections())
grouping = new BeatmapCarouselFilterGrouping(() => Criteria!, () => realm)
Copy link
Member

Choose a reason for hiding this comment

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

why was this moved?

Copy link
Member Author

Choose a reason for hiding this comment

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

Since I'm including realm directly, I thought there's not really any reason to still have the detachedCollections func. If you don't think that's the correct approach and the realm func will eventually be removed, then I'll revert it.

private Sample? spinSample;
private Sample? randomSelectSample;

private Func<List<BeatmapCollection>> detachedCollections = null!;
Copy link
Member

Choose a reason for hiding this comment

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

Why?

{
var allLocalScores = r.All<ScoreInfo>()
.Filter($"{nameof(ScoreInfo.User)}.{nameof(RealmUser.OnlineID)} == $0"
+ $" && {nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.Hash)} == {nameof(ScoreInfo.BeatmapHash)}"
Copy link
Member

Choose a reason for hiding this comment

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

What's this conditional?

Copy link
Member Author

Choose a reason for hiding this comment

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

Carried across from TopLocalRank (also exists in LeaderboardManager), it seems to be for discarding scores of a beatmap version that doesn't match the current one.

Copy link
Member

Choose a reason for hiding this comment

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

Inline comment mentioning this is the case, please. I can't read brains.

+ $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1"
+ $" && {nameof(ScoreInfo.DeletePending)} == false", criteria.LocalUserId, criteria.Ruleset?.ShortName)
.OrderByDescending(s => s.TotalScore)
.ThenBy(s => s.Date);
Copy link
Member

Choose a reason for hiding this comment

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

Date has limited precision. Online score id should be preferred whenever available.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also carried from https://github.com/ppy/osu/blob/cb354685ca323f4e429e60617077ed5a5175ee3d/osu.Game/Screens/Select/Carousel/TopLocalRank.cs#L78-L77, I can switch it to fall back to ID but we should probably update that code as well? If so, should I bundle it in this PR or in a separate one.

Copy link
Member

Choose a reason for hiding this comment

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

Update both places, and add a comment inline here mentioning the important fact that this code is shared.

{
Debug.Assert(score.BeatmapInfo != null);

if (topRankMapping.ContainsKey(score.BeatmapInfo))
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason to not use hash here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I was debating between hash and the database ID, but the latter seemed to represent the beatmap in the local user's database better. This seems to be an either/or situation to me?

Copy link
Member

Choose a reason for hiding this comment

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

The issue I have here is using a model class as the key. I don't care if you use ID or hash, but please use it directly.

Rank animation is played for new panels when scrolling down, showing the
previous rank belonging to the previous beatmap assigned to that
specific panel instance.
@peppy peppy self-requested a review August 12, 2025 05:08
@peppy peppy force-pushed the rank-achieved-grouping branch from 49bb206 to 8239294 Compare August 12, 2025 05:26
#region Benchmarks

[Test]
public void TestPerformance()
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 your own improvement, when i asked for a benchmark i hoped to be able to run something headless via nunit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add "rank achieved" grouping mode to song select

2 participants