Closes https://github.com/ppy/osu/issues/29738.
This "regressed" in https://github.com/ppy/osu/pull/29639, but if I
didn't know better, I'd go as far as saying that this looks like a .NET
bug, because the fact that PR broke it looks not sane.
The TL;DR on this is that before the pull in question, the offending
`.Contains()` check that this commit modifies was called on a
`List<BeatmapSetInfo>`. The pull changed the collection type to
`BeatmapSetInfo[]`. That said, the call is a LINQ call, so all good,
right?
Not really. First off, the default overload resolution order means that
the previous code would call `List<T>.Contains()`, and not
`Enumerable.Contains<T>()`. Then again, why would that matter? In both
cases `T` is still `BeatmapSetInfo`, right? Well... about that...
It is difficult to tell for sure what precisely is happening here,
because of what looks like runtime magic. The end *symptom* is that the
old code ended up calling `Array<BeatmapSetInfo>.IndexOf()`, and the new
code ends up calling... `Array<object>.IndexOf()`.
So while yes, `BeatmapSetInfo` implements `IEquatable` and
the expectation would be that `Equals<BeatmapSetInfo>()` should be
getting called, the type elision to `object` means that we're back to
reference equality semantics, because that's what
`EqualityComparer.Default<object>` is.
A five-minute github search across dotnet/runtime yields this:
c4792a228e/src/coreclr/vm/array.cpp (L984-L990)
Now again, if I didn't know better, I'd see that "OPTIMIZATION:"
comment, see what transpired in this scenario, and call that
optimisation invalid, because it changes semantics. But I *probably*
know that the dotnet team knows better and am probably just going to
take it for what it is, because blame on that code looks to be years
old and it's probably not a new behaviour. (I haven't tested empirically
if it is.)
Instead the fix is just to tell the `.Contains()` method to use the
correct comparer.
As reported (very poorly) in
https://github.com/ppy/osu/pull/28991#issuecomment-2331854970.
I believe this is a total edge case and is mostly visible on dev due to
some beatmaps existing on `osu.ppy.sh` and not on `dev.ppy.sh`, but I
tend to agree in general that these types of failures should not be
firing very loud error notifications; logging to network and disabling
the button with a tooltip adjustment should be enough.
This is an interesting scenario where we arrive at a fresh
`BeatmapOffsetControl` but with a reference score (from the last play).
Our best assumption here is that the beatmap's offset hasn't changed
since the last play, so we want to use it for the `lastPlayBeatmapOffset`.
But due to unfortunate order of execution, `Current.Value` was not yet
initialised.
It didn't ever really make sense for it to be sharing the implementation
details of that (e.g. colouring of primary/dangerous actions), and with
the hotkey display things got outright hacky, so I'm decoupling it
entirely.
analysis container creates settings and the settings items are created more nicely
also removed use of localized strings because that can be done separately
The explicit detach call was removed from `updateBeatmapSet`, causing
this to occur. We could optionally add it back (it will be a noop in all
cases though).
These used to work because there was a huge blocking load operation,
which is now more asynchronous.
Note that the change made in `SongSelect` is not required, but defensive
(feels it should have been doing this the whole time).
The only exception to the rule here was "when screen isn't active apply
without debounce" but I'm not sure we want this. It would cause a
stutter on returning to song select and I'm not even sure this is a
common scenario.
I'd rather remove it and see if someone finds an actual case where this
is an issue.
This reverts a portion of https://github.com/ppy/osu/pull/9539.
The rearrangement in `SongSelect` is required to get the initial filter
into `BeatmapCarousel` (and avoid the `FilterChanged` event firing,
causing a delayed/scheduled filter application).
I've been meaning to make the progress bar synchronise with the beat
rather than a continuous countdown, just to give the overlay a bit more
of a rhythmic feel.
Not completely happy with how this feels but I think it's a start?
I had to refactor how the break overlay works in the process. It no
longer creates transforms for all breaks ahead-of-time, which could be
argued as a better way of doing things. It's more dynamically able to
handle breaks now (maybe useful for the future, who knows).
I didn't really want to move the cursor in front of the HUD, but we face
a bit of an impossible scenario otherwise (it should definitely be in
front of the break overlay for visibility).
So I'll deal with it for now.