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

Fix timeline snapping ignoring nearby timing points #29435

Merged
merged 7 commits into from
Aug 21, 2024

Conversation

OliBomby
Copy link
Contributor

closes #22720

Now objects in the timeline will consider the next timing point as a tick to snap to.

This implementation does a slight modification to the binary search logic that finds timing points. If there are multiple timing points at the same exact time, the new implementation will get the rightmost element (highest index) of all the exact matches. The old implementation would return the first match found, which is kind of undefined behaviour which I can't have for the TimingPointAfter method.


if (time >= list[^1].Time)
return list[^1];
int index = BinarySearchUtils.BinarySearch(list, time, c => c.Time, EqualitySelection.Rightmost);
Copy link
Contributor

@smoogipoo smoogipoo Aug 16, 2024

Choose a reason for hiding this comment

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

Just for a bit of commentary, I think this matches osu!stable better (ControlPointAt / BeatLengthAt), but I will run a diffcalc to be sure.

There is ControlPointAtBin which appears to only be used to play sounds in mania, that isn't doing right-most, but I would say it's unlikely to cause any differences.

@smoogipoo
Copy link
Contributor

!diffcalc

@smoogipoo
Copy link
Contributor

!diffcalc
RULESET=taiko

@smoogipoo
Copy link
Contributor

!diffcalc
RULESET=catch

@smoogipoo
Copy link
Contributor

!diffcalc
RULESET=mania

Copy link

github-actions bot commented Aug 16, 2024

Copy link

github-actions bot commented Aug 16, 2024

Copy link

github-actions bot commented Aug 16, 2024

Copy link

github-actions bot commented Aug 16, 2024

@bdach bdach self-requested a review August 19, 2024 11:44
/// <param name="termFunc">Function that maps an item in the list to its index property.</param>
/// <param name="equalitySelection">Determines which index to return if there are multiple exact matches.</param>
/// <returns>The index of the found item. Will return the complement of the index of the first item greater than the search query if no exact match is found.</returns>
public static int BinarySearch<T, T2>(IReadOnlyList<T> list, T2 searchTerm, Func<T, T2> termFunc, EqualitySelection equalitySelection = EqualitySelection.FirstFound)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not a huge fan of the decision to make this have two typeparams. Would prefer one, and doing the necessary data projection (termFunc) in usages before calling this. It just complicates this method for no reason as far as I'm concerned.

I'd understand if the projection was expensive, then there'd be an argument that it's reducing the number of invocations of termFunc, but no, it's just a .Time access. If anything you may get better results by dumping the times off to a list before passing them to this method (waves hands about cache locality).

Either that or just move the method back inline where it used to live. I don't really get why generalise this if it's gonna have one caller ever.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The purpose of termFunc is to make the method generic while keeping it simple for the caller and not increasing time complexity.

If I were to go without termFunc then the caller would have to do data projection before calling the binary search which can be slow because you have to create a new array with all the time values. Creating a data projection in linear time is no better than just using linear search. Or the caller has to manage an array with the time values and keep it in sync with the items which is way more complicated.
Therefore I think termFunc is the best solution for a generic binary search.

I understand your argument about not generalising. If its only ever going to be used with timing points then there's no need to generalise the function. But are we really going to never use binary search again in the future?

Copy link
Collaborator

@bdach bdach Aug 19, 2024

Choose a reason for hiding this comment

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

We probably will and we even already are, but many places just do it locally and that is probably fine. Run a grep for BinarySearch on master and you'll see.

After having stepped on similar rakes too many times in the past I want to see that something will generalise before allowing someone to generalise. No generalising without at least two usages, preferably more.

Also in general not huge on generics these days either. Generics as implemented in C# introduce a lot of rigidity that is very difficult to root out later, so the less they are used the better in my book.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok I see we use the built in binary search for lists a lot. I'll move the method back inline.

@bdach
Copy link
Collaborator

bdach commented Aug 21, 2024

I can't see any issues in manual inspection, but the binary search implementation has visibly changed, so out of an overabundance of caution---

!diffcalc

(I'll probably do only one just as a sanity check)

Copy link

github-actions bot commented Aug 21, 2024

@peppy peppy self-requested a review August 21, 2024 16:33
@peppy peppy merged commit a943000 into ppy:master Aug 21, 2024
13 checks passed
@OliBomby OliBomby deleted the improve-snaps branch August 21, 2024 16:56
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.

Editor timeline can snap to non-existent tick on BPM change
4 participants