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

Remove CachedModelDependencyContainer and OnlinePlayComposite from multiplayer #30634

Merged
merged 35 commits into from
Nov 20, 2024

Conversation

smoogipoo
Copy link
Contributor

@smoogipoo smoogipoo commented Nov 15, 2024

Prereqs:

Please read previous PRs #30633, #30631, #30598, and #30592 for a gradual explanation of the important changes made.

@peppy peppy self-requested a review November 15, 2024 07:07
Copy link
Member

@peppy peppy left a comment

Choose a reason for hiding this comment

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

Posting partial review, 60/103 files in.

/// <summary>
/// Delay before the background is loaded while on-screen.
/// </summary>
public double BackgroundLoadDelay = 500;
Copy link
Member

Choose a reason for hiding this comment

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

Minor gripe but I think these feel better as explicit properties. I see fields like this and expect them to be constants.

diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs
index 8ddda485b5..be6ca43f4b 100644
--- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs
+++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs
@@ -19,12 +19,12 @@ public partial class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable<IBe
         /// <summary>
         /// Delay before the background is loaded while on-screen.
         /// </summary>
-        public double BackgroundLoadDelay = 500;
+        public double BackgroundLoadDelay { get; set; } = 500;
 
         /// <summary>
         /// Delay before the background is unloaded while off-screen.
         /// </summary>
-        public double BackgroundUnloadDelay = 10000;
+        public double BackgroundUnloadDelay { get; set; } = 10000;
 
         [Resolved]
         private BeatmapManager beatmaps { get; set; } = null!;


throw new AggregateException(exceptionText.ToString());
}
Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item;
Copy link
Member

Choose a reason for hiding this comment

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

This is still a mouthful to parse. But I guess apart from splitting out variables there's not much to be done.

diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index 998a34931d..7105a3fc91 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -771,8 +771,16 @@ public Task PlaylistItemChanged(MultiplayerPlaylistItem item)
 
                 Debug.Assert(APIRoom != null);
 
-                Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item;
-                APIRoom.Playlist = APIRoom.Playlist.Select((pi, i) => pi.ID == item.ID ? new PlaylistItem(item) : APIRoom.Playlist[i]).ToArray();
+                var existingItem = Room.Playlist.Single(existing => existing.ID == item.ID);
+                Room.Playlist[Room.Playlist.IndexOf(existingItem)] = item;
+
+                APIRoom.Playlist = APIRoom.Playlist.Select((pi, i) =>
+                {
+                    if (pi.ID == item.ID)
+                        return new PlaylistItem(item);
+
+                    return APIRoom.Playlist[i];
+                }).ToArray();
 
                 ItemChanged?.Invoke(item);
                 RoomUpdated?.Invoke();

Marginally better maybe?

Copy link
Contributor Author

@smoogipoo smoogipoo Nov 19, 2024

Choose a reason for hiding this comment

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

Can you just think of this as temporary? I really don't want to duplicate 10 lines of code across 3 files, and in one case where existingItem is already a local variable so there's even going to be different syntactical structures to the code...

Copy link
Member

Choose a reason for hiding this comment

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

Sure

Comment on lines +83 to +84
private void updateRoomPlaylist()
=> playlist.Items.ReplaceRange(0, playlist.Items.Count, room.Playlist);
Copy link
Member

Choose a reason for hiding this comment

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

I think this is going to require some better handling. It can be pretty slow to do a full reload of this list:

osu.2024-11-15.at.07.33.18.mp4

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be fixed by ppy/osu-framework#6425

Comment on lines +205 to +206
if (a.User == null)
return false;
Copy link
Member

Choose a reason for hiding this comment

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

Should this not be removed (ie. return true) if it's unwanted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've done my best to preserve existing semantics in this PR to not cause unexpected side-effects. Unless you can prove to me otherwise, this will not be changing.

Copy link
Member

@peppy peppy left a comment

Choose a reason for hiding this comment

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

First pass completed.

@@ -164,7 +164,7 @@ private void load(AudioManager audio)
new DrawableMatchRoom(Room, allowEdit)
{
OnEdit = () => settingsOverlay.Show(),
SelectedItem = { BindTarget = SelectedItem }
SelectedItem = SelectedItem
Copy link
Member

Choose a reason for hiding this comment

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

I'm really hesitant about this one. Assigning directly to a bindable always feels wrong. Reading back on the previous PR, the rationale seems to be that "this means we won't forget to set it"? I dunno how valid that is. Would keeping the old BindTarget method still be feasible?

And yes, we use BindableWithCurrent elsewhere like this. The difference I see is that in all those usages (to my knowledge), the property is named Current, which signifies that it has the special underlying behaviour.

In addition, we're passing this multiple levels down (see MultiplayerMatchFooter / MatchStartControl as one example). We usually tend to use DI in such a case. Could we create a MultiplayerState cached class for containing things like this as an alternative? Potentially also containing Room which seems to be passed everywhere?

Copy link
Contributor Author

@smoogipoo smoogipoo Nov 15, 2024

Choose a reason for hiding this comment

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

I think this is one of the most important parts of this series of PRs...

the rationale seems to be that "this means we won't forget to set it"?

It enforces a contract, in the same way a ctor enforces a contract. BindTarget/BindTo and all that work, but it must always be assigned. At compile time it says that if you haven't assigned this you're definitely doing something wrong, so think about what you want to do now.
In previous code this would've been an ambient non-nullable resolved bindable that enforced the same contract at run-time, which imo is very hidden, especially for something that potentially has multiple sources (I've only applied this to SelectedItem, which is exactly this case).

the property is named Current, which signifies that it has the special underlying behaviour.

This is not significance to me because I made no effort to use or reproduce IHasCurrentValue<>. I only wanted to enforce the contract.

Could we create a MultiplayerState cached class for containing things like this as an alternative?

To be honest, if you want that then this series of PRs has no need to exist. Because, the natural follow up is - if we BDL Room, then we might as well keep every property in Room also bindable. Most of the complexity of this PR goes away I believe.

It's a different take on things. I can't say whether that would be a bad idea, so I'm going to block this PR as I investigate that path instead.

Copy link
Member

Choose a reason for hiding this comment

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

As discussed IRL, my proposed direction is likely not where we want to go. I can agree that we should avoid caching "states" to DI, especially when those states are not singleton (smoogi mentioned that if we have states here, there will need to be multiple layers to handle lobby/room).

So let's leave things how they are for now.

Comment on lines +372 to +379
updateRoomName();
updateRoomType();
updateRoomQueueMode();
updateRoomPassword();
updateRoomAutoSkip();
updateRoomMaxParticipants();
updateRoomAutoStartDuration();
updateRoomPlaylist();
Copy link
Member

Choose a reason for hiding this comment

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

a bit painful..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm aware and mentioned as such in #30631.

@peppy
Copy link
Member

peppy commented Nov 15, 2024

FWIW I've run tests locally for over an hour

TestSceneMultiplayer
TestSceneMultiplayerMatchSubScreen

No failures.. promising 👍

Copy link
Collaborator

@bdach bdach left a comment

Choose a reason for hiding this comment

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

Not sure if it'll be useful but I gave this a review pass.

Generally I will say I don't have the warmest of feelings towards INotifyPropertyChanged as an interface from prior WPF exposure to it, and it doesn't feel great to basically use yet another method of "notify changed" given we have bindables which are already intended for this expressly. Another thing is that lists don't mix super well with INotifyPropertyChanged because you can't raise a collection changed event, and so all consumers, if wanting to be efficient, will have to do their local diffing.

That said, from prior experience with daily challenge, multiplayer did feel a little "too boxed in" abstraction-wise. I'm not 100% sure whether this PR helps much in that respect since it seems to be focused more on pure mechanics of change notifications, but I'm willing to try it in pursuit of new features.

Overall I'm neutral on this change. Willing to let this chain run its paces and see what happens.

Comment on lines +293 to 317
private void updateRoomName()
{
if (roomName != null)
roomName.Text = Room.Name;
}

private void updateRoomCategory()
{
if (Room.Category > RoomCategory.Normal)
specialCategoryPill?.Show();
else
specialCategoryPill?.Hide();
}

private void updateRoomType()
{
if (endDateInfo != null)
endDateInfo.Alpha = Room.Type == MatchType.Playlists ? 1 : 0;
}

private void updateRoomHasPassword()
{
if (passwordIcon != null)
passwordIcon.Alpha = Room.HasPassword ? 1 : 0;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Related to comment above - I probably would have merged these into one method, from experience. I'm talking about every case where these groups of small updateX() methods have materialised; in my experience the efficiency tradeoff has rarely been worth the mental overhead.

Copy link
Contributor Author

@smoogipoo smoogipoo Nov 19, 2024

Choose a reason for hiding this comment

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

I expected these PRs to be reviewed part by part and not in the final thing. I won't do the NRT changes next time and keep it to one PR, my bad.

In each prior PR I wrote a little explanation of what I believe to be the important parts. I mentioned that I'm aware of this separation in #30631, and am also not convinced with it/will clean it up.

Copy link
Collaborator

@bdach bdach Nov 19, 2024

Choose a reason for hiding this comment

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

I expected these PRs to be reviewed part by part and not in the final thing

I would have done so, but given that seemingly a full review of this diff was done already here, it felt like the wrong thing to review piece-by-piece now...?

@smoogipoo
Copy link
Contributor Author

Closed all other PRs and linked them in the description here because it seems everyone is reading this PR. Apologies for the size :/

@smoogipoo
Copy link
Contributor Author

My thinking going into INotifyPropertyChanged is that MultiplayerClient already does the same thing, and for it to use bindables would be very annoying.

In #30592 I mentioned that I'm not fully sold on the current implementation/location but this is a first step.

@smoogipoo smoogipoo removed the blocked Don't merge this. label Nov 19, 2024
@peppy peppy self-requested a review November 20, 2024 07:34
Didn't really work to fix these tests due to the sticky nature of the
failure. Also I can no longer reproduce locally, so the hope is that
these are fixed by ppy#30634.
@peppy peppy merged commit fabeb6f into ppy:master Nov 20, 2024
8 of 10 checks passed
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.

3 participants