Closes https://github.com/ppy/osu/issues/25247.
The scenario involved here is as follows:
1. User completes beatmap by hitting all notes.
2. `checkScoreCompleted()` determines completion, and calls
`progressToResults(withDelay: true)`.
3. `progressToResults()` schedules `resultsDisplayDelegate`, which
includes a call to `prepareAndImportScoreAsync()`, a second in the
future.
4. User presses quick retry hotkey.
This calls `Player.Restart(quickRestart: true)`,
which invokes `Player.RestartRequested`,
which in turn calls `PlayerLoader.restartRequested(true)`,
which in turn causes `PlayerLoader` to make itself current,
which means that `Player.OnExiting()` will get called.
5. `Player.OnExiting()` sees that `prepareScoreForDisplayTask` is null
(because `prepareAndImportScoreAsync()` - which sets it -
is scheduled to happen in the future), and as such assumes that
the score did not complete. Thus, it marks the score as failed.
6. `Player.Restart()` after invoking `RestartRequested` calls
`PerformExit(false)`, which then will unconditionally call
`prepareAndImportScoreAsync()`. But the score has already been marked
as failed.
The flow above can be described as "convoluted", but I'm not sure I have
it in me right now to try and refactor it again. Therefore, to fix,
switch the `prepareScoreForDisplayTask` null check in
`Player.OnExiting()` to check `GameplayState.HasPassed` instead, as it
is not susceptible to the same out-of-order read issue.
- Closes https://github.com/ppy/osu/issues/25248
- Possibly also closes https://github.com/ppy/osu/issues/20475
Regressed in e33486a766.
`StopUsingBeatmapClock()` intends to, as the name says, stop operating
on the working beatmap clock to yield its usage to other components on
exit. As part of that it tries to unapply audio adjustments so that
other screens can apply theirs freely instead.
However, the aforementioned commit introduced a bug in this. Previously
to it, `track` was an alias for the `SourceClock`, which could be
mutated in an indirect way via `ChangeSource()` calls. The
aforementioned commit made `track` a `readonly` field, initialised in
constructor, which would _never_ change value. In particular, it would
_always_ be the beatmap track, which meant that
`StopUsingBeatmapClock()` would remove the adjustments from the beatmap
track, but then at the end of the method, _apply them onto that same
track again_.
This was only saved by the fact that clock adjustments are removed again
on disposal of the `MasterGameplayClockContainer()`. This - due to async
disposal pressure - could explain infrequently reported cases wherein
the track would just continue to speed up ad infinitum.
To fix, fully substitute the beatmap track for a virtual track at the
point of calling `StopUsingBeatmapClock()`.
It wasn't correctly checking the current underlying health, which could
be zero in usages of `AccumulatingHealthProcessor`, for instance.
Closes#25046.
I've gone ahead and matched the osu!stable behaviour for this, as it
seems like it's what people are used to and they will settle for no
less.
Closes https://github.com/ppy/osu/issues/18089.
The reasoning is explained in the inline comment, but basically this was
getting blocked by `isPaused` being in an initial `true` state (as it is
on construction), while the source clock was still `IsRunning`.
There's no real guarantee of sync between the source and the `isPaused`
bindable right now. Maybe there should be in the future, but to restore
sanity, let's ensure that a call to `Reset` can at least stop the track
as we expect.