1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 07:33:20 +08:00

Make Room.RecentParticipants non-bindable

This commit is contained in:
Dan Balasescu 2024-11-14 15:07:16 +09:00
parent dc5337d771
commit b16edbbf52
No known key found for this signature in database
11 changed files with 100 additions and 79 deletions

View File

@ -199,11 +199,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
if (room.RecentParticipants.Count == 0) if (room.RecentParticipants.Count == 0)
{ {
room.RecentParticipants.AddRange(Enumerable.Range(0, 20).Select(i => new APIUser room.RecentParticipants = Enumerable.Range(0, 20).Select(i => new APIUser
{ {
Id = i, Id = i,
Username = $"User {i}" Username = $"User {i}"
})); }).ToArray();
} }
return new DrawableLoungeRoom(room) return new DrawableLoungeRoom(room)

View File

@ -138,17 +138,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void addUser(int id) private void addUser(int id)
{ {
SelectedRoom.Value.RecentParticipants.Add(new APIUser SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Append(new APIUser
{ {
Id = id, Id = id,
Username = $"User {id}" Username = $"User {id}"
}); }).ToArray();
SelectedRoom.Value.ParticipantCount++; SelectedRoom.Value.ParticipantCount++;
} }
private void removeUserAt(int index) private void removeUserAt(int index)
{ {
SelectedRoom.Value.RecentParticipants.RemoveAt(index); SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Where(u => !u.Equals(SelectedRoom.Value.RecentParticipants[index])).ToArray();
SelectedRoom.Value.ParticipantCount--; SelectedRoom.Value.ParticipantCount--;
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
@ -19,17 +20,16 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("create list", () => AddStep("create list", () =>
{ {
SelectedRoom.Value = new Room { RoomID = 7 }; SelectedRoom.Value = new Room
for (int i = 0; i < 50; i++)
{ {
SelectedRoom.Value.RecentParticipants.Add(new APIUser RoomID = 7,
RecentParticipants = Enumerable.Range(0, 50).Select(_ => new APIUser
{ {
Username = "peppy", Username = "peppy",
Statistics = new UserStatistics { GlobalRank = 1234 }, Statistics = new UserStatistics { GlobalRank = 1234 },
Id = 2 Id = 2
}); }).ToArray()
} };
}); });
} }

View File

@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Playlists
{ {
room.Name = "my awesome room"; room.Name = "my awesome room";
room.Host = API.LocalUser.Value; room.Host = API.LocalUser.Value;
room.RecentParticipants.Add(room.Host); room.RecentParticipants = [room.Host];
room.EndDate = DateTimeOffset.Now.AddMinutes(5); room.EndDate = DateTimeOffset.Now.AddMinutes(5);
room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First())
{ {
@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.Playlists
room.Name = "my awesome room"; room.Name = "my awesome room";
room.MaxAttempts = 5; room.MaxAttempts = 5;
room.Host = API.LocalUser.Value; room.Host = API.LocalUser.Value;
room.RecentParticipants.Add(room.Host); room.RecentParticipants = [room.Host];
room.EndDate = DateTimeOffset.Now.AddMinutes(5); room.EndDate = DateTimeOffset.Now.AddMinutes(5);
room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First()) room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First())
{ {

View File

@ -487,11 +487,11 @@ namespace osu.Game.Online.Multiplayer
{ {
Debug.Assert(APIRoom != null); Debug.Assert(APIRoom != null);
APIRoom.RecentParticipants.Add(user.User ?? new APIUser APIRoom.RecentParticipants = APIRoom.RecentParticipants.Append(user.User ?? new APIUser
{ {
Id = user.UserID, Id = user.UserID,
Username = "[Unresolved]" Username = "[Unresolved]"
}); }).ToArray();
APIRoom.ParticipantCount++; APIRoom.ParticipantCount++;
} }
@ -506,7 +506,7 @@ namespace osu.Game.Online.Multiplayer
PlayingUserIds.Remove(user.UserID); PlayingUserIds.Remove(user.UserID);
Debug.Assert(APIRoom != null); Debug.Assert(APIRoom != null);
APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID); APIRoom.RecentParticipants = APIRoom.RecentParticipants.Where(u => u.Id != user.UserID).ToArray();
APIRoom.ParticipantCount--; APIRoom.ParticipantCount--;
callback?.Invoke(user); callback?.Invoke(user);

View File

@ -137,6 +137,15 @@ namespace osu.Game.Online.Rooms
set => SetField(ref participantCount, value); set => SetField(ref participantCount, value);
} }
/// <summary>
/// The set of most recent participants in the room.
/// </summary>
public IReadOnlyList<APIUser> RecentParticipants
{
get => recentParticipants;
set => SetList(ref recentParticipants, value);
}
/// <summary> /// <summary>
/// The match type. /// The match type.
/// </summary> /// </summary>
@ -282,6 +291,9 @@ namespace osu.Game.Online.Rooms
[JsonProperty("participant_count")] [JsonProperty("participant_count")]
private int participantCount; private int participantCount;
[JsonProperty("recent_participants")]
private IReadOnlyList<APIUser> recentParticipants = [];
[JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)]
private int? maxAttempts; private int? maxAttempts;
@ -324,10 +336,6 @@ namespace osu.Game.Online.Rooms
[JsonProperty("playlist")] [JsonProperty("playlist")]
public readonly BindableList<PlaylistItem> Playlist = new BindableList<PlaylistItem>(); public readonly BindableList<PlaylistItem> Playlist = new BindableList<PlaylistItem>();
[Cached]
[JsonProperty("recent_participants")]
public readonly BindableList<APIUser> RecentParticipants = new BindableList<APIUser>();
/// <summary> /// <summary>
/// Copies values from another <see cref="Room"/> into this one. /// Copies values from another <see cref="Room"/> into this one.
/// </summary> /// </summary>
@ -369,11 +377,7 @@ namespace osu.Game.Online.Rooms
Playlist.AddRange(other.Playlist); Playlist.AddRange(other.Playlist);
} }
if (!RecentParticipants.SequenceEqual(other.RecentParticipants)) RecentParticipants = other.RecentParticipants;
{
RecentParticipants.Clear();
RecentParticipants.AddRange(other.RecentParticipants);
}
} }
public void RemoveExpiredPlaylistItems() public void RemoveExpiredPlaylistItems()
@ -411,6 +415,16 @@ namespace osu.Game.Online.Rooms
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!) protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetList<T>(ref IReadOnlyList<T> list, IReadOnlyList<T> value, [CallerMemberName] string propertyName = null!)
{
if (list.SequenceEqual(value))
return false;
list = value;
OnPropertyChanged(propertyName);
return true;
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null!) protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null!)
{ {
if (EqualityComparer<T>.Default.Equals(field, value)) if (EqualityComparer<T>.Default.Equals(field, value))

View File

@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
AddInternal(scroll = new OsuScrollContainer(direction) AddInternal(scroll = new OsuScrollContainer(direction)
{ {
Child = list = new ParticipantsList() Child = list = new ParticipantsList(room)
}); });
switch (direction) switch (direction)

View File

@ -1,15 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Users.Drawables; using osu.Game.Users.Drawables;
using osuTK; using osuTK;
@ -57,15 +56,29 @@ namespace osu.Game.Screens.OnlinePlay.Components
} }
} }
[BackgroundDependencyLoader] private readonly Room room;
private void load()
public ParticipantsList(Room room)
{ {
RecentParticipants.CollectionChanged += (_, _) => updateParticipants(); this.room = room;
}
protected override void LoadComplete()
{
base.LoadComplete();
room.PropertyChanged += onRoomPropertyChanged;
updateParticipants(); updateParticipants();
} }
private ScheduledDelegate scheduledUpdate; private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
private FillFlowContainer<UserTile> tiles; {
if (e.PropertyName == nameof(Room.RecentParticipants))
updateParticipants();
}
private ScheduledDelegate? scheduledUpdate;
private FillFlowContainer<UserTile>? tiles;
private void updateParticipants() private void updateParticipants()
{ {
@ -83,8 +96,8 @@ namespace osu.Game.Screens.OnlinePlay.Components
Spacing = Vector2.One Spacing = Vector2.One
}; };
for (int i = 0; i < RecentParticipants.Count; i++) for (int i = 0; i < room.RecentParticipants.Count; i++)
tiles.Add(new UserTile { User = RecentParticipants[i] }); tiles.Add(new UserTile { User = room.RecentParticipants[i] });
AddInternal(tiles); AddInternal(tiles);
@ -92,9 +105,15 @@ namespace osu.Game.Screens.OnlinePlay.Components
}); });
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
private partial class UserTile : CompositeDrawable private partial class UserTile : CompositeDrawable
{ {
public APIUser User public APIUser? User
{ {
get => avatar.User; get => avatar.User;
set => avatar.User = value; set => avatar.User = value;

View File

@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Specialized; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -165,12 +164,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{ {
base.LoadComplete(); base.LoadComplete();
RecentParticipants.BindCollectionChanged(onParticipantsChanged, true);
room.PropertyChanged += onRoomPropertyChanged; room.PropertyChanged += onRoomPropertyChanged;
updateRoomHost(); updateRoomHost();
updateRoomParticipantCount(); updateRoomParticipantCount();
updateRoomParticipants();
} }
private int numberOfCircles = 4; private int numberOfCircles = 4;
@ -190,44 +188,39 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
// Reinitialising the list looks janky, but this is unlikely to be used in a setting where it's visible. // Reinitialising the list looks janky, but this is unlikely to be used in a setting where it's visible.
clearUsers(); clearUsers();
foreach (var u in RecentParticipants) foreach (var u in room.RecentParticipants)
addUser(u); addUser(u);
updateHiddenUsers(); updateHiddenUsers();
} }
} }
private void onParticipantsChanged(object? sender, NotifyCollectionChangedEventArgs e) private void updateRoomParticipants()
{ {
switch (e.Action) HashSet<APIUser> newUsers = room.RecentParticipants.ToHashSet();
avatarFlow.RemoveAll(a =>
{ {
case NotifyCollectionChangedAction.Add: // Avatar with no user. Really shouldn't ever be the case but asserting it correctly is difficult.
Debug.Assert(e.NewItems != null); if (a.User == null)
return false;
foreach (var added in e.NewItems.OfType<APIUser>()) // User was previously and still is a participant. Keep them around but remove them from the new set.
addUser(added); // This will be useful when we add all remaining users (now just the new participants) to the flow.
break; if (newUsers.Contains(a.User))
{
case NotifyCollectionChangedAction.Remove: newUsers.Remove(a.User);
Debug.Assert(e.OldItems != null); return false;
foreach (var removed in e.OldItems.OfType<APIUser>())
removeUser(removed);
break;
case NotifyCollectionChangedAction.Reset:
clearUsers();
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
// Easiest is to just reinitialise the whole list. These are unlikely to ever be use cases.
clearUsers();
foreach (var u in RecentParticipants)
addUser(u);
break;
} }
// User is no longer a participant. Remove them from the flow.
return true;
}, true);
// Add all remaining users to the flow.
foreach (var u in newUsers)
addUser(u);
updateHiddenUsers(); updateHiddenUsers();
} }
@ -239,11 +232,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
avatarFlow.Add(new CircularAvatar { User = user }); avatarFlow.Add(new CircularAvatar { User = user });
} }
private void removeUser(APIUser user)
{
avatarFlow.RemoveAll(a => a.User == user, true);
}
private void clearUsers() private void clearUsers()
{ {
avatarFlow.Clear(); avatarFlow.Clear();
@ -253,7 +241,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private void updateHiddenUsers() private void updateHiddenUsers()
{ {
int hiddenCount = 0; int hiddenCount = 0;
if (RecentParticipants.Count > NumberOfCircles) if (room.RecentParticipants.Count > NumberOfCircles)
hiddenCount = room.ParticipantCount - NumberOfCircles + 1; hiddenCount = room.ParticipantCount - NumberOfCircles + 1;
hiddenUsers.Count = hiddenCount; hiddenUsers.Count = hiddenCount;
@ -262,7 +250,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
avatarFlow.Remove(avatarFlow.Last(), true); avatarFlow.Remove(avatarFlow.Last(), true);
else if (displayedCircles < NumberOfCircles) else if (displayedCircles < NumberOfCircles)
{ {
var nextUser = RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u)); var nextUser = room.RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u));
if (nextUser != null) addUser(nextUser); if (nextUser != null) addUser(nextUser);
} }
} }
@ -278,6 +266,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
case nameof(Room.ParticipantCount): case nameof(Room.ParticipantCount):
updateRoomParticipantCount(); updateRoomParticipantCount();
break; break;
case nameof(Room.RecentParticipants):
updateRoomParticipants();
break;
} }
} }

View File

@ -4,7 +4,6 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay namespace osu.Game.Screens.OnlinePlay
@ -16,8 +15,5 @@ namespace osu.Game.Screens.OnlinePlay
{ {
[Resolved(typeof(Room))] [Resolved(typeof(Room))]
protected BindableList<PlaylistItem> Playlist { get; private set; } = null!; protected BindableList<PlaylistItem> Playlist { get; private set; } = null!;
[Resolved(typeof(Room))]
protected BindableList<APIUser> RecentParticipants { get; private set; } = null!;
} }
} }

View File

@ -280,7 +280,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
responseRoom.Password = responseRoom.HasPassword ? Guid.NewGuid().ToString() : null; responseRoom.Password = responseRoom.HasPassword ? Guid.NewGuid().ToString() : null;
if (!withParticipants) if (!withParticipants)
responseRoom.RecentParticipants.Clear(); responseRoom.RecentParticipants = [];
return responseRoom; return responseRoom;
} }