On Android, users were unable to join or create multiplayer rooms. The
root cause of that was that the both the wait and set of the
`ManualResetEvent` in `getRoomUsers` occurred on the same thread, which
created a chicken-and-egg situation - the set could not proceed until
the wait had actually completed.
Resolve by substituting the `ManualResetEvent` for a
`TaskCompletionSource` to achieve a promise-style task, which the
previous code was a crude approximation of anyway.
Closes#11385.
For wheel input with precision, we still prefer exact tracking for now.
May change this in the future based on feedback from mappers, but it
makes little sense to do non-snapped scrolling when input is coming from
a non-precise source.
The previous code would run a calcaulation for the beatmap's own ruleset
if the current one failed. While this does make sense, with the current
way we use this component (and the implementation flow) it is quite unsafe.
The to the call on `.Result` in the `catch` block, this would 100%
deadlock due to the thread concurrency of the `ThreadedTaskScheduler`
being 1. Even if the nested run could be run inline (it should be), the
task scheduler won't even get to the point of checking whether this is
feasible due to it being saturated by the already running task.
I'm not sure if we still need this fallback lookup logic. After removing
it, it's feasible that 0 stars will be returned during the scenario that
previously caused a deadlock, but I don't necessarily think this is
incorrect. There may be another reason for this needing to exist which
I'm not aware of (diffcalc?) but if that's the case we may want to move
the try-catch handling to the point of usage.
To reproduce the deadlock scenario with 100% success (the repro
instructions in the linked issue aren't that simple and require some
patience and good timing), the main portion of the lookup can be changed
to randomly trigger a nested lookup:
```
if (RNG.NextSingle() > 0.5f)
return GetAsync(new
DifficultyCacheLookup(key.Beatmap, key.Beatmap.Ruleset,
key.OrderedMods)).Result;
else
return new StarDifficulty(attributes);
```
After switching beatmap once or twice, pausing debug and viewing the
state of threads should show exactly what is going on.
It turns out this polling was necessary to get extra data that isn't
included in the main listing request. It was removed deemed useless, and
in order to fix the order of rooms changing when selecting a room.
Weirdly, I can't reproduce this happening any more, and on close
inspection of the code can't see how it could happen in the first place.
For now, let's revert this change and iterate from there, if/when the
same issue arises again.
I've discussed avoiding this second poll by potentially including more
data (just `user_id`s?) in the main listing request, but not 100% sure
on this - even if the returned data is minimal it's an extra join
server-side, which could cause performance issues for large numbers of
rooms.
In stable, the type of legacy judgement to show is based on the presence
of particle textures in the skin. We were using the skin version
instead, which turns out to be incorrect and not what some user skins
expect.
Closes#11078.
The previous code would run a calcaulation for the beatmap's own ruleset
if the current one failed. While this does make sense, with the current
way we use this component (and the implementation flow) it is quite unsafe.
The to the call on `.Result` in the `catch` block, this would 100%
deadlock due to the thread concurrency of the `ThreadedTaskScheduler`
being 1. Even if the nested run could be run inline (it should be), the
task scheduler won't even get to the point of checking whether this is
feasible due to it being saturated by the already running task.
I'm not sure if we still need this fallback lookup logic. After removing
it, it's feasible that 0 stars will be returned during the scenario that
previously caused a deadlock, but I don't necessarily think this is
incorrect. There may be another reason for this needing to exist which
I'm not aware of (diffcalc?) but if that's the case we may want to move
the try-catch handling to the point of usage.
To reproduce the deadlock scenario with 100% success (the repro
instructions in the linked issue aren't that simple and require some
patience and good timing), the main portion of the lookup can be changed
to randomly trigger a nested lookup:
```
if (RNG.NextSingle() > 0.5f)
return GetAsync(new
DifficultyCacheLookup(key.Beatmap, key.Beatmap.Ruleset,
key.OrderedMods)).Result;
else
return new StarDifficulty(attributes);
```
After switching beatmap once or twice, pausing debug and viewing the
state of threads should show exactly what is going on.
`onLoadRequested()` always released the `readyClickOperation` ongoing
operation, without checking whether it actually needs to/should (it
should only do so if the action initiating the operation was starting
the game by the host). This would crash all other consumers, who already
released the operation when their ready-up operation completed server
side.
To resolve, relax the constraint such that the operation can be ended
multiple times in any order. At the end of the day the thing that
matters is that the operation is done and the ready button is unblocked.
It turns out this polling was necessary to get extra data that isn't
included in the main listing request. It was removed deemed useless, and
in order to fix the order of rooms changing when selecting a room.
Weirdly, I can't reproduce this happening any more, and on close
inspection of the code can't see how it could happen in the first place.
For now, let's revert this change and iterate from there, if/when the
same issue arises again.
I've discussed avoiding this second poll by potentially including more
data (just `user_id`s?) in the main listing request, but not 100% sure
on this - even if the returned data is minimal it's an extra join
server-side, which could cause performance issues for large numbers of
rooms.
Finishing an operation started via
`OngoingOperationTracker.BeginOperation()` was risky in cases where the
operation ended at a callback on another thread (which, in the case of
multiplayer, is *most* cases). In particular, if any consumer registered
a callback that mutates transforms when the operation ends, it would
result in crashes after the framework-side safety checks.
Rework `OngoingOperationTracker` into an always-present component
residing in the drawable hierarchy, and ensure that the
`operationInProgress` bindable is always updated on the update thread.
This way consumers don't have to add local schedules in multiple places.