-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Rewrite match subscreen to remove bindables #32669
Conversation
using osuTK; | ||
using Container = osu.Framework.Graphics.Containers.Container; | ||
using ParticipantsList = osu.Game.Screens.OnlinePlay.Multiplayer.Participants.ParticipantsList; | ||
|
||
namespace osu.Game.Screens.OnlinePlay.Multiplayer | ||
{ | ||
[Cached] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to address #32250 (comment), I've kept the subscreen being cached as-is, simplifying the settings overlay implementation to invoke ShowSongSelection()
.
LoadComponent(userModsSelectOverlay = new MultiplayerUserModSelectOverlay | ||
{ | ||
Beatmap = { BindTarget = Beatmap } | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NB: In #32250, the beatmap wasn't bound which caused the footer to not update.
cf6d937
to
286b3d9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well this one didn't get much easier to review.
Spotted a few things from reading the code, once those are fixed I'll probably just do a test in-game and click buttons if I can't break it.
/// Responds to changes in the active room to adjust the visibility of the settings and main content. | ||
/// Only the settings overlay is visible while the room isn't created, and only the main content is visible after creation. | ||
/// </summary> | ||
private void onRoomUpdated() => Scheduler.AddOnce(() => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scheduler.AddOnce()
does not debounce correctly if it's given an inline lambda like this because it's going to fail an equality check framework-side to determine if it should debounce or not. The two options are to either make the body of this a named method (this is the most idiomatic one we use), or to utilise the closure-eliminating overload of this, and have the inner lambda be a static (MultiplayerMatchSubScreen screen) => { }
(don't recommend that one).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get it. Have you actually tested this? This is very idiomatic code in multiplayer, and it would also mean stuff like this is broken:
osu/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomSounds.cs
Lines 42 to 55 in 3852cca
private void onUserJoined(MultiplayerRoomUser user) | |
=> Scheduler.AddOnce(() => userJoinedSample?.Play()); | |
private void onUserLeft(MultiplayerRoomUser user) | |
=> Scheduler.AddOnce(() => userLeftSample?.Play()); | |
private void onUserKicked(MultiplayerRoomUser user) | |
=> Scheduler.AddOnce(() => userKickedSample?.Play()); | |
private void onHostChanged(MultiplayerRoomUser? host) | |
{ | |
if (host != null) | |
Scheduler.AddOnce(() => hostChangedSample?.Play()); | |
} |
Which was added not by me, here: ef5e37c. So two people apparently believe this method works completely differently than it actually does. If this is the case I will do everything in my power to obliterate this method.
See also this o!f test: https://github.com/ppy/osu-framework/blob/5a7056a434be77ff84ecffddd27ac5617b400dac/osu.Framework.Tests/Threading/SchedulerTest.cs#L427-L439
Edit: I can't repro with:
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index d464362fda..cb84ae4706 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -416,6 +416,9 @@ protected override void LoadComplete()
onRoomUpdated();
updateGameplayState();
updateUserActivity();
+
+ onRoomUpdated();
+ onRoomUpdated();
}
/// <summary>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I have not tested this specific code with a debugger. I did not remember the test above existed. I wrote something like
[Test]
public void TestAddOnce()
{
var scheduler = new Scheduler();
int invocations = 0;
scheduler.AddOnce(() => invocations++);
scheduler.AddOnce(() => invocations++);
scheduler.Update();
Assert.That(invocations, Is.EqualTo(1));
}
and that fails. Which I guess isn't strictly the same what you have because you're never repeating that lambda again, but as that code comment in the test you linked says, I've at this point internalised that AddOnce()
should not be called on inline lambdas because there be dragons and it might or it might not work as intended.
See ppy/osu-framework#6023 (comment) even, which is where that PR comment originates from, and #25076 (comment), which sparked it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the way it's conceptualised is that the lambda instance itself is always the same. i.e.
static object? a;
void m()
{
a ??= new object();
}
static object? b;
static object? c;
void n()
{
b ??= new object();
c ??= new object();
}
The usage in multiplayer is always a case of the former.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The usage in multiplayer is always a case of the former.
That's not even it actually. If you pull up the IL display in rider and put it to low level C# (for convenience) you'll see this:
private void onRoomUpdated()
{
this.Scheduler.AddOnce(new Action((object) this, __methodptr(<onRoomUpdated>b__77_0)));
}
This is allocating a delegate instance every time, but the compiler has figured out that it can make a method group delegate out of the body of the lambda, so the delegate equality check inside scheduler doesn't fail (because the lambdas are attached to a named method). This'll only work as long as the compiler does that and not something else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you fine with it as is then? As I said this is pretty standard through all of multiplayer, feels weird if we're applying any changes only to this usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose we can keep it until a problem sure.
osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empirically tested this some. Found only one thing, namely that clicking the countdown button on the bottom bar crashes with
[runtime] 2025-04-08 08:42:03 [verbose]: Too many unhandled exceptions, crashing out.
Unhandled exception. System.InvalidOperationException: Cannot show or hide a popover without a parent PopoverContainer in the hierarchy
at osu.Framework.Extensions.PopoverExtensions.setTargetOnNearestPopover(Drawable origin, IHasPopover target)
at osu.Framework.Extensions.PopoverExtensions.ShowPopover(IHasPopover hasPopover)
at osu.Framework.Graphics.Containers.ClickableContainer.OnClick(ClickEvent e)
at osu.Game.Graphics.UserInterface.OsuAnimatedButton.OnClick(ClickEvent e) in /Users/bdach/Documents/Work/open-source/osu/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs:line 117
at osu.Framework.Input.ButtonEventManager`1.PropagateButtonEvent(IEnumerable`1 drawables, UIEvent e)
at osu.Framework.Input.MouseButtonEventManager.handleClick(InputState state, List`1 targets)
at osu.Framework.Input.MouseButtonEventManager.HandleButtonUp(InputState state, List`1 targets)
Good catch, didn't have any tests for that. |
Beyond isolation, this stuff is also used to test (de-)serialisation. Much-of-a-muchness in this case since it isn't too thoroughly tested, but best do it anyway.
Fixes #32503
Split out from #32250, with minor changes.
This finalises the refactorings that have been going on for a while now.
In a similar fashion to #31882, this is a full rewrite of
MultiplayerMatchSubScreen
that no longer inherits (and finally removes)RoomSubScreen
. The primary goal here is to remove the beatmap/ruleset/mod/"selected item" bindables and useMultiplayerRoom
states.Because it's a rewrite, I suggest going through the screen anew (rather than looking at the diff) and possibly comparing to the implementation of
PlaylistsRoomSubScreen
.