1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-11 02:17:19 +08:00

Move room tracking to lounge subscreen

This commit is contained in:
Dan Balasescu 2025-02-12 18:35:35 +09:00
parent d3f6f0953a
commit 068a66e7d4
No known key found for this signature in database
8 changed files with 95 additions and 150 deletions

View File

@ -50,17 +50,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add rooms", () => RoomManager.AddRooms(5, withSpotlightRooms: true));
AddAssert("has 5 rooms", () => container.Rooms.Count == 5);
AddAssert("has 5 rooms", () => container.DrawableRooms.Count == 5);
AddAssert("all spotlights at top", () => container.Rooms
AddAssert("all spotlights at top", () => container.DrawableRooms
.SkipWhile(r => r.Room.Category == RoomCategory.Spotlight)
.All(r => r.Room.Category == RoomCategory.Normal));
AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.First(r => r.RoomID == 0)));
AddAssert("has 4 rooms", () => container.Rooms.Count == 4);
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID != 0));
AddAssert("has 4 rooms", () => container.DrawableRooms.Count == 4);
AddAssert("first room removed", () => container.DrawableRooms.All(r => r.Room.RoomID != 0));
AddStep("select first room", () => container.Rooms.First().TriggerClick());
AddStep("select first room", () => container.DrawableRooms.First().TriggerClick());
AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight)));
AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID)!));
@ -137,15 +137,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add rooms", () => RoomManager.AddRooms(4));
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
AddUntilStep("4 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 4);
AddStep("filter one room", () => container.Filter.Value = new FilterCriteria { SearchString = "1" });
AddUntilStep("1 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 1);
AddUntilStep("1 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 1);
AddStep("remove filter", () => container.Filter.Value = null);
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
AddUntilStep("4 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 4);
}
[Test]
@ -156,13 +156,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
// Todo: What even is this case...?
AddStep("set empty filter criteria", () => container.Filter.Value = new FilterCriteria());
AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5);
AddUntilStep("5 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 5);
AddStep("filter osu! rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo });
AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
AddUntilStep("2 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 2);
AddStep("filter catch rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo });
AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
AddUntilStep("3 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 3);
}
[Test]
@ -176,15 +176,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("apply default filter", () => container.Filter.SetDefault());
AddUntilStep("both rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
AddUntilStep("both rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 2);
AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Public });
AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword));
AddUntilStep("private room hidden", () => container.DrawableRooms.All(r => !r.Room.HasPassword));
AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Private });
AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword));
AddUntilStep("public room hidden", () => container.DrawableRooms.All(r => r.Room.HasPassword));
}
[Test]

View File

@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Playlists
public void TestManyRooms()
{
AddStep("add rooms", () => RoomManager.AddRooms(500));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 500);
AddUntilStep("wait for rooms", () => roomsContainer.DrawableRooms.Count == 500);
}
[Test]
@ -45,45 +45,45 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("reset mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddStep("add rooms", () => RoomManager.AddRooms(30));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30);
AddUntilStep("wait for rooms", () => roomsContainer.DrawableRooms.Count == 30);
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.DrawableRooms[0]));
AddStep("move mouse to third room", () => InputManager.MoveMouseTo(roomsContainer.Rooms[2]));
AddStep("move mouse to third room", () => InputManager.MoveMouseTo(roomsContainer.DrawableRooms[2]));
AddStep("hold down", () => InputManager.PressButton(MouseButton.Left));
AddStep("drag to top", () => InputManager.MoveMouseTo(roomsContainer.Rooms[0]));
AddStep("drag to top", () => InputManager.MoveMouseTo(roomsContainer.DrawableRooms[0]));
AddAssert("first and second room masked", ()
=> !checkRoomVisible(roomsContainer.Rooms[0]) &&
!checkRoomVisible(roomsContainer.Rooms[1]));
=> !checkRoomVisible(roomsContainer.DrawableRooms[0]) &&
!checkRoomVisible(roomsContainer.DrawableRooms[1]));
}
[Test]
public void TestScrollSelectedIntoView()
{
AddStep("add rooms", () => RoomManager.AddRooms(30));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30);
AddUntilStep("wait for rooms", () => roomsContainer.DrawableRooms.Count == 30);
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.DrawableRooms[0]));
AddStep("select last room", () => roomsContainer.Rooms[^1].TriggerClick());
AddStep("select last room", () => roomsContainer.DrawableRooms[^1].TriggerClick());
AddUntilStep("first room is masked", () => !checkRoomVisible(roomsContainer.Rooms[0]));
AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.Rooms[^1]));
AddUntilStep("first room is masked", () => !checkRoomVisible(roomsContainer.DrawableRooms[0]));
AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.DrawableRooms[^1]));
}
[Test]
public void TestEnteringRoomTakesLeaseOnSelection()
{
AddStep("add rooms", () => RoomManager.AddRooms(1));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 1);
AddUntilStep("wait for rooms", () => roomsContainer.DrawableRooms.Count == 1);
AddAssert("selected room is not disabled", () => !loungeScreen.SelectedRoom.Disabled);
AddStep("select room", () => roomsContainer.Rooms[0].TriggerClick());
AddStep("select room", () => roomsContainer.DrawableRooms[0].TriggerClick());
AddAssert("selected room is non-null", () => loungeScreen.SelectedRoom.Value != null);
AddStep("enter room", () => roomsContainer.Rooms[0].TriggerClick());
AddStep("enter room", () => roomsContainer.DrawableRooms[0].TriggerClick());
AddUntilStep("wait for match load", () => Stack.CurrentScreen is PlaylistsRoomSubScreen);

View File

@ -1,9 +1,9 @@
// 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.
using System;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
@ -15,23 +15,8 @@ namespace osu.Game.Screens.OnlinePlay.Components
/// </summary>
public partial class ListingPollingComponent : RoomPollingComponent
{
public IBindable<bool> InitialRoomsReceived => initialRoomsReceived;
private readonly Bindable<bool> initialRoomsReceived = new Bindable<bool>();
public readonly Bindable<FilterCriteria?> Filter = new Bindable<FilterCriteria?>();
[BackgroundDependencyLoader]
private void load()
{
Filter.BindValueChanged(_ =>
{
RoomManager.ClearRooms();
initialRoomsReceived.Value = false;
if (IsLoaded)
PollImmediately();
});
}
public required Action<Room[]> RoomsReceived { get; init; }
public readonly IBindable<FilterCriteria?> Filter = new Bindable<FilterCriteria?>();
private GetRoomsRequest? lastPollRequest;
@ -43,26 +28,14 @@ namespace osu.Game.Screens.OnlinePlay.Components
if (Filter.Value == null)
return base.Poll();
var tcs = new TaskCompletionSource<bool>();
lastPollRequest?.Cancel();
var tcs = new TaskCompletionSource<bool>();
var req = new GetRoomsRequest(Filter.Value);
req.Success += result =>
{
result = result.Where(r => r.Category != RoomCategory.DailyChallenge).ToList();
foreach (var existing in RoomManager.Rooms.ToArray())
{
if (result.All(r => r.RoomID != existing.RoomID))
RoomManager.RemoveRoom(existing);
}
foreach (var incoming in result)
RoomManager.AddOrUpdateRoom(incoming);
initialRoomsReceived.Value = true;
RoomsReceived(result.Where(r => r.Category != RoomCategory.DailyChallenge).ToArray());
tcs.SetResult(true);
};
@ -71,6 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
API.Queue(req);
lastPollRequest = req;
return tcs.Task;
}
}

View File

@ -28,15 +28,14 @@ namespace osu.Game.Screens.OnlinePlay.Components
if (room.RoomID == null)
return base.Poll();
var tcs = new TaskCompletionSource<bool>();
lastPollRequest?.Cancel();
var tcs = new TaskCompletionSource<bool>();
var req = new GetRoomRequest(room.RoomID.Value);
req.Success += result =>
{
RoomManager.AddOrUpdateRoom(result);
room.CopyFrom(result);
tcs.SetResult(true);
};

View File

@ -7,10 +7,8 @@ using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
@ -24,17 +22,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class RoomsContainer : CompositeDrawable, IKeyBindingHandler<GlobalAction>
{
public readonly BindableList<Room> Rooms = new BindableList<Room>();
public readonly Bindable<Room?> SelectedRoom = new Bindable<Room?>();
public readonly Bindable<FilterCriteria?> Filter = new Bindable<FilterCriteria?>();
public IReadOnlyList<DrawableRoom> Rooms => roomFlow.FlowingChildren.Cast<DrawableRoom>().ToArray();
public IReadOnlyList<DrawableRoom> DrawableRooms => roomFlow.FlowingChildren.Cast<DrawableRoom>().ToArray();
private readonly IBindableList<Room> rooms = new BindableList<Room>();
private readonly FillFlowContainer<DrawableLoungeRoom> roomFlow;
[Resolved]
private IRoomManager roomManager { get; set; } = null!;
// handle deselection
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
@ -62,11 +57,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
protected override void LoadComplete()
{
rooms.CollectionChanged += roomsChanged;
roomManager.RoomsUpdated += updateSorting;
rooms.BindTo(roomManager.Rooms);
Rooms.BindCollectionChanged(roomsChanged, true);
Filter.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true);
}
@ -155,7 +146,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private void addRooms(IEnumerable<Room> rooms)
{
foreach (var room in rooms)
roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = SelectedRoom });
{
var drawableRoom = new DrawableLoungeRoom(room) { SelectedRoom = SelectedRoom };
roomFlow.Add(drawableRoom);
// Always show spotlight playlists at the top of the listing.
roomFlow.SetLayoutPosition(drawableRoom, room.Category > RoomCategory.Normal ? float.MinValue : -(room.RoomID ?? 0));
}
applyFilterCriteria(Filter.Value);
}
@ -181,17 +179,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
SelectedRoom.Value = null;
}
private void updateSorting()
{
foreach (var room in roomFlow)
{
roomFlow.SetLayoutPosition(room, room.Room.Category > RoomCategory.Normal
// Always show spotlight playlists at the top of the listing.
? float.MinValue
: -(room.Room.RoomID ?? 0));
}
}
protected override bool OnClick(ClickEvent e)
{
if (!SelectedRoom.Disabled)
@ -226,7 +213,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
if (SelectedRoom.Disabled)
return;
var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent);
var visibleRooms = DrawableRooms.AsEnumerable().Where(r => r.IsPresent);
Room? room;
@ -246,13 +233,5 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
#endregion
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (roomManager.IsNotNull())
roomManager.RoomsUpdated -= updateSorting;
}
}
}

View File

@ -53,8 +53,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
AutoSizeAxes = Axes.Both
};
protected ListingPollingComponent ListingPollingComponent { get; private set; } = null!;
protected readonly Bindable<Room?> SelectedRoom = new Bindable<Room?>();
[Resolved]
@ -75,12 +73,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
[Resolved]
protected OsuConfigManager Config { get; private set; } = null!;
private IDisposable? joiningRoomOperation { get; set; }
private IDisposable? joiningRoomOperation;
private LeasedBindable<Room?>? selectionLease;
private readonly BindableList<Room> rooms = new BindableList<Room>();
private readonly Bindable<FilterCriteria?> filter = new Bindable<FilterCriteria?>();
private readonly Bindable<bool> hasListingResults = new Bindable<bool>();
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
private readonly IBindable<bool> isIdle = new BindableBool();
private ListingPollingComponent listingPollingComponent = null!;
private PopoverContainer popoverContainer = null!;
private LoadingLayer loadingLayer = null!;
private RoomsContainer roomsContainer = null!;
@ -100,7 +101,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
InternalChildren = new Drawable[]
{
ListingPollingComponent = CreatePollingComponent().With(c => c.Filter.BindTarget = filter),
listingPollingComponent = new ListingPollingComponent
{
RoomsReceived = onListingReceived,
Filter = { BindTarget = filter }
},
popoverContainer = new PopoverContainer
{
Name = @"Rooms area",
@ -116,8 +121,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
ScrollbarOverlapsContent = false,
Child = roomsContainer = new RoomsContainer
{
Rooms = { BindTarget = rooms },
SelectedRoom = { BindTarget = SelectedRoom },
Filter = { BindTarget = filter },
SelectedRoom = { BindTarget = SelectedRoom }
}
},
},
@ -178,7 +184,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
// scroll selected room into view on selection.
SelectedRoom.BindValueChanged(val =>
{
var drawable = roomsContainer.Rooms.FirstOrDefault(r => r.Room == val.NewValue);
var drawable = roomsContainer.DrawableRooms.FirstOrDefault(r => r.Room == val.NewValue);
if (drawable != null)
scrollContainer.ScrollIntoView(drawable);
});
@ -190,7 +196,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
searchTextBox.Current.BindValueChanged(_ => updateFilterDebounced());
ruleset.BindValueChanged(_ => UpdateFilter());
isIdle.BindValueChanged(_ => updatePollingRate(this.IsCurrentScreen()), true);
if (ongoingOperationTracker != null)
@ -199,11 +204,38 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
operationInProgress.BindValueChanged(_ => updateLoadingLayer());
}
ListingPollingComponent.InitialRoomsReceived.BindValueChanged(_ => updateLoadingLayer(), true);
hasListingResults.BindValueChanged(_ => updateLoadingLayer());
filter.BindValueChanged(_ =>
{
rooms.Clear();
hasListingResults.Value = false;
listingPollingComponent.PollImmediately();
});
updateFilter();
}
private void onListingReceived(Room[] result)
{
Dictionary<long, Room> localRoomsById = rooms.ToDictionary(r => r.RoomID!.Value);
Dictionary<long, Room> resultRoomsById = result.ToDictionary(r => r.RoomID!.Value);
// Remove all local rooms no longer in the result set.
rooms.RemoveAll(r => !resultRoomsById.ContainsKey(r.RoomID!.Value));
// Add or update local rooms with the result set.
foreach (var r in result)
{
if (localRoomsById.TryGetValue(r.RoomID!.Value, out Room? existingRoom))
existingRoom.CopyFrom(r);
else
rooms.Add(r);
}
hasListingResults.Value = true;
}
#region Filtering
public void UpdateFilter() => Scheduler.AddOnce(updateFilter);
@ -267,7 +299,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
onReturning();
// Poll for any newly-created rooms (including potentially the user's own).
ListingPollingComponent.PollImmediately();
listingPollingComponent.PollImmediately();
}
public override bool OnExiting(ScreenExitEvent e)
@ -392,11 +424,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
this.Push(CreateRoomSubScreen(room));
}
public void RefreshRooms() => ListingPollingComponent.PollImmediately();
public void RefreshRooms() => listingPollingComponent.PollImmediately();
private void updateLoadingLayer()
{
if (operationInProgress.Value || !ListingPollingComponent.InitialRoomsReceived.Value)
if (operationInProgress.Value || !hasListingResults.Value)
loadingLayer.Show();
else
loadingLayer.Hide();
@ -405,11 +437,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
private void updatePollingRate(bool isCurrentScreen)
{
if (!isCurrentScreen)
ListingPollingComponent.TimeBetweenPolls.Value = 0;
listingPollingComponent.TimeBetweenPolls.Value = 0;
else
ListingPollingComponent.TimeBetweenPolls.Value = isIdle.Value ? 120000 : 15000;
listingPollingComponent.TimeBetweenPolls.Value = isIdle.Value ? 120000 : 15000;
Logger.Log($"Polling adjusted (listing: {ListingPollingComponent.TimeBetweenPolls.Value})");
Logger.Log($"Polling adjusted (listing: {listingPollingComponent.TimeBetweenPolls.Value})");
}
protected abstract OsuButton CreateNewRoomButton();
@ -421,7 +453,5 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
protected abstract Room CreateNewRoom();
protected abstract RoomSubScreen CreateRoomSubScreen(Room room);
protected abstract ListingPollingComponent CreatePollingComponent();
}
}

View File

@ -79,8 +79,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room);
protected override ListingPollingComponent CreatePollingComponent() => new MultiplayerListingPollingComponent();
protected override void JoinInternal(Room room, string? password, Action<Room> onSuccess, Action<string> onFailure)
{
client.JoinRoom(room, password).ContinueWith(result =>
@ -109,37 +107,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
base.OpenNewRoom(room);
}
private partial class MultiplayerListingPollingComponent : ListingPollingComponent
{
[Resolved]
private MultiplayerClient client { get; set; } = null!;
private readonly IBindable<bool> isConnected = new Bindable<bool>();
[BackgroundDependencyLoader]
private void load()
{
isConnected.BindTo(client.IsConnected);
isConnected.BindValueChanged(_ => Scheduler.AddOnce(poll), true);
}
private void poll()
{
if (isConnected.Value && IsLoaded)
PollImmediately();
}
protected override Task Poll()
{
if (!isConnected.Value)
return Task.CompletedTask;
if (client.Room != null)
return Task.CompletedTask;
return base.Poll();
}
}
}
}

View File

@ -11,7 +11,6 @@ using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match;
@ -87,8 +86,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new PlaylistsRoomSubScreen(room);
protected override ListingPollingComponent CreatePollingComponent() => new ListingPollingComponent();
private enum PlaylistsCategory
{
Any,