1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-27 13:23:05 +08:00

Merge pull request #16944 from peppy/rooms-request-faster

Update playlists/multiplayer to use new compact response
This commit is contained in:
Dan Balasescu 2022-02-24 23:20:07 +09:00 committed by GitHub
commit 16a3bbbcb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 121 additions and 51 deletions

View File

@ -36,6 +36,8 @@ namespace osu.Game.Online.API
public string WebsiteRootUrl { get; } public string WebsiteRootUrl { get; }
public int APIVersion => 20220217; // We may want to pull this from the game version eventually.
public Exception LastLoginError { get; private set; } public Exception LastLoginError { get; private set; }
public string ProvidedUsername { get; private set; } public string ProvidedUsername { get; private set; }

View File

@ -2,6 +2,7 @@
// 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; using System;
using System.Globalization;
using JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.IO.Network; using osu.Framework.IO.Network;
@ -112,6 +113,9 @@ namespace osu.Game.Online.API
WebRequest = CreateWebRequest(); WebRequest = CreateWebRequest();
WebRequest.Failed += Fail; WebRequest.Failed += Fail;
WebRequest.AllowRetryOnTimeout = false; WebRequest.AllowRetryOnTimeout = false;
WebRequest.AddHeader("x-api-version", API.APIVersion.ToString(CultureInfo.InvariantCulture));
if (!string.IsNullOrEmpty(API.AccessToken)) if (!string.IsNullOrEmpty(API.AccessToken))
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}"); WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");

View File

@ -33,6 +33,8 @@ namespace osu.Game.Online.API
public string WebsiteRootUrl => "http://localhost"; public string WebsiteRootUrl => "http://localhost";
public int APIVersion => int.Parse(DateTime.Now.ToString("yyyyMMdd"));
public Exception LastLoginError { get; private set; } public Exception LastLoginError { get; private set; }
/// <summary> /// <summary>

View File

@ -57,6 +57,11 @@ namespace osu.Game.Online.API
/// </summary> /// </summary>
string WebsiteRootUrl { get; } string WebsiteRootUrl { get; }
/// <summary>
/// The version of the API.
/// </summary>
int APIVersion { get; }
/// <summary> /// <summary>
/// The last login error that occurred, if any. /// The last login error that occurred, if any.
/// </summary> /// </summary>

View File

@ -12,6 +12,7 @@ using osu.Game.Users;
namespace osu.Game.Online.API.Requests.Responses namespace osu.Game.Online.API.Requests.Responses
{ {
[JsonObject(MemberSerialization.OptIn)]
public class APIUser : IEquatable<APIUser>, IUser public class APIUser : IEquatable<APIUser>, IUser
{ {
[JsonProperty(@"id")] [JsonProperty(@"id")]

View File

@ -62,6 +62,10 @@ namespace osu.Game.Online.Rooms
[JsonProperty("beatmap_id")] [JsonProperty("beatmap_id")]
private int onlineBeatmapId => Beatmap.OnlineID; private int onlineBeatmapId => Beatmap.OnlineID;
/// <summary>
/// A beatmap representing this playlist item.
/// In many cases, this will *not* contain any usable information apart from OnlineID.
/// </summary>
[JsonIgnore] [JsonIgnore]
public IBeatmapInfo Beatmap { get; set; } = null!; public IBeatmapInfo Beatmap { get; set; } = null!;

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -15,6 +14,7 @@ using osu.Game.Utils;
namespace osu.Game.Online.Rooms namespace osu.Game.Online.Rooms
{ {
[JsonObject(MemberSerialization.OptIn)]
public class Room : IDeepCloneable<Room> public class Room : IDeepCloneable<Room>
{ {
[Cached] [Cached]
@ -37,8 +37,19 @@ namespace osu.Game.Online.Rooms
[JsonProperty("channel_id")] [JsonProperty("channel_id")]
public readonly Bindable<int> ChannelId = new Bindable<int>(); public readonly Bindable<int> ChannelId = new Bindable<int>();
[JsonProperty("current_playlist_item")]
[Cached]
public readonly Bindable<PlaylistItem> CurrentPlaylistItem = new Bindable<PlaylistItem>();
[JsonProperty("playlist_item_stats")]
[Cached]
public readonly Bindable<RoomPlaylistItemStats> PlaylistItemStats = new Bindable<RoomPlaylistItemStats>();
[JsonProperty("difficulty_range")]
[Cached]
public readonly Bindable<RoomDifficultyRange> DifficultyRange = new Bindable<RoomDifficultyRange>();
[Cached] [Cached]
[JsonIgnore]
public readonly Bindable<RoomCategory> Category = new Bindable<RoomCategory>(); public readonly Bindable<RoomCategory> Category = new Bindable<RoomCategory>();
// Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106) // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106)
@ -51,19 +62,15 @@ namespace osu.Game.Online.Rooms
} }
[Cached] [Cached]
[JsonIgnore]
public readonly Bindable<int?> MaxAttempts = new Bindable<int?>(); public readonly Bindable<int?> MaxAttempts = new Bindable<int?>();
[Cached] [Cached]
[JsonIgnore]
public readonly Bindable<RoomStatus> Status = new Bindable<RoomStatus>(new RoomStatusOpen()); public readonly Bindable<RoomStatus> Status = new Bindable<RoomStatus>(new RoomStatusOpen());
[Cached] [Cached]
[JsonIgnore]
public readonly Bindable<RoomAvailability> Availability = new Bindable<RoomAvailability>(); public readonly Bindable<RoomAvailability> Availability = new Bindable<RoomAvailability>();
[Cached] [Cached]
[JsonIgnore]
public readonly Bindable<MatchType> Type = new Bindable<MatchType>(); public readonly Bindable<MatchType> Type = new Bindable<MatchType>();
// Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106) // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106)
@ -76,7 +83,6 @@ namespace osu.Game.Online.Rooms
} }
[Cached] [Cached]
[JsonIgnore]
public readonly Bindable<QueueMode> QueueMode = new Bindable<QueueMode>(); public readonly Bindable<QueueMode> QueueMode = new Bindable<QueueMode>();
[JsonConverter(typeof(SnakeCaseStringEnumConverter))] [JsonConverter(typeof(SnakeCaseStringEnumConverter))]
@ -88,7 +94,6 @@ namespace osu.Game.Online.Rooms
} }
[Cached] [Cached]
[JsonIgnore]
public readonly Bindable<int?> MaxParticipants = new Bindable<int?>(); public readonly Bindable<int?> MaxParticipants = new Bindable<int?>();
[Cached] [Cached]
@ -113,7 +118,6 @@ namespace osu.Game.Online.Rooms
public readonly Bindable<string> Password = new Bindable<string>(); public readonly Bindable<string> Password = new Bindable<string>();
[Cached] [Cached]
[JsonIgnore]
public readonly Bindable<TimeSpan?> Duration = new Bindable<TimeSpan?>(); public readonly Bindable<TimeSpan?> Duration = new Bindable<TimeSpan?>();
[JsonProperty("duration")] [JsonProperty("duration")]
@ -158,6 +162,8 @@ namespace osu.Game.Online.Rooms
var copy = new Room(); var copy = new Room();
copy.CopyFrom(this); copy.CopyFrom(this);
// ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not.
copy.RoomID.Value = null; copy.RoomID.Value = null;
return copy; return copy;
@ -183,6 +189,9 @@ namespace osu.Game.Online.Rooms
EndDate.Value = other.EndDate.Value; EndDate.Value = other.EndDate.Value;
UserScore.Value = other.UserScore.Value; UserScore.Value = other.UserScore.Value;
QueueMode.Value = other.QueueMode.Value; QueueMode.Value = other.QueueMode.Value;
DifficultyRange.Value = other.DifficultyRange.Value;
PlaylistItemStats.Value = other.PlaylistItemStats.Value;
CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value;
if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value)
Status.Value = new RoomStatusEnded(); Status.Value = new RoomStatusEnded();
@ -211,21 +220,27 @@ namespace osu.Game.Online.Rooms
Playlist.RemoveAll(i => i.Expired); Playlist.RemoveAll(i => i.Expired);
} }
#region Newtonsoft.Json implicit ShouldSerialize() methods [JsonObject(MemberSerialization.OptIn)]
public class RoomPlaylistItemStats
{
[JsonProperty("count_active")]
public int CountActive;
// The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases. [JsonProperty("count_total")]
// They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed public int CountTotal;
// unless the fields are also renamed.
[UsedImplicitly] [JsonProperty("ruleset_ids")]
public bool ShouldSerializeRoomID() => false; public int[] RulesetIDs;
}
[UsedImplicitly] [JsonObject(MemberSerialization.OptIn)]
public bool ShouldSerializeHost() => false; public class RoomDifficultyRange
{
[JsonProperty("min")]
public double Min;
[UsedImplicitly] [JsonProperty("max")]
public bool ShouldSerializeEndDate() => false; public double Max;
}
#endregion
} }
} }

View File

@ -23,6 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
{ {
InternalChild = sprite = CreateBackgroundSprite(); InternalChild = sprite = CreateBackgroundSprite();
CurrentPlaylistItem.BindValueChanged(_ => updateBeatmap());
Playlist.CollectionChanged += (_, __) => updateBeatmap(); Playlist.CollectionChanged += (_, __) => updateBeatmap();
updateBeatmap(); updateBeatmap();
@ -30,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
private void updateBeatmap() private void updateBeatmap()
{ {
sprite.Beatmap.Value = Playlist.GetCurrentItem()?.Beatmap; sprite.Beatmap.Value = CurrentPlaylistItem.Value?.Beatmap ?? Playlist.GetCurrentItem()?.Beatmap;
} }
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };

View File

@ -2,7 +2,6 @@
// 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; using System;
using System.Collections.Specialized;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -75,15 +74,29 @@ namespace osu.Game.Screens.OnlinePlay.Components
{ {
base.LoadComplete(); base.LoadComplete();
Playlist.BindCollectionChanged(updateRange, true); DifficultyRange.BindValueChanged(_ => updateRange());
Playlist.BindCollectionChanged((_, __) => updateRange(), true);
} }
private void updateRange(object sender, NotifyCollectionChangedEventArgs e) private void updateRange()
{ {
var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray(); StarDifficulty minDifficulty;
StarDifficulty maxDifficulty;
StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); if (DifficultyRange.Value != null)
StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); {
minDifficulty = new StarDifficulty(DifficultyRange.Value.Min, 0);
maxDifficulty = new StarDifficulty(DifficultyRange.Value.Max, 0);
}
else
{
// In multiplayer rooms, the beatmaps of playlist items will not be populated to a point this can be correct.
// Either populating them via BeatmapLookupCache or polling the API for the room's DifficultyRange will be required.
var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray();
minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0);
maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0);
}
minDisplay.Current.Value = minDifficulty; minDisplay.Current.Value = minDifficulty;
maxDisplay.Current.Value = maxDifficulty; maxDisplay.Current.Value = maxDifficulty;

View File

@ -388,7 +388,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
SelectedItem.BindValueChanged(onSelectedItemChanged, true); CurrentPlaylistItem.BindValueChanged(onSelectedItemChanged, true);
} }
private CancellationTokenSource beatmapLookupCancellation; private CancellationTokenSource beatmapLookupCancellation;

View File

@ -1,7 +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.Collections.Specialized; using System.Linq;
using Humanizer; using Humanizer;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Extensions.LocalisationExtensions;
@ -41,15 +41,22 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{ {
base.LoadComplete(); base.LoadComplete();
Playlist.BindCollectionChanged(updateCount, true); PlaylistItemStats.BindValueChanged(_ => updateCount());
Playlist.BindCollectionChanged((_, __) => updateCount(), true);
} }
private void updateCount(object sender, NotifyCollectionChangedEventArgs e) private void updateCount()
{ {
int activeItems = Playlist.Count > 0 || PlaylistItemStats.Value == null
// For now, use the playlist as the source of truth if it has any items.
// This allows the count to display correctly on the room screen (after joining a room).
? Playlist.Count(i => !i.Expired)
: PlaylistItemStats.Value.CountActive;
count.Clear(); count.Clear();
count.AddText(Playlist.Count.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); count.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold));
count.AddText(" "); count.AddText(" ");
count.AddText("Beatmap".ToQuantity(Playlist.Count, ShowQuantityAs.None)); count.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None));
} }
} }
} }

View File

@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{ {
bool matchingFilter = true; bool matchingFilter = true;
matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.RulesetID == criteria.Ruleset.OnlineID); matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false;
if (!string.IsNullOrEmpty(criteria.SearchString)) if (!string.IsNullOrEmpty(criteria.SearchString))
matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase));

View File

@ -343,7 +343,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
base.LoadComplete(); base.LoadComplete();
drawablePlaylist.Items.BindTo(Playlist); drawablePlaylist.Items.BindTo(Playlist);
drawablePlaylist.SelectedItem.BindTo(SelectedItem); drawablePlaylist.SelectedItem.BindTo(CurrentPlaylistItem);
} }
protected override void Update() protected override void Update()
@ -419,7 +419,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
if (text.StartsWith(not_found_prefix, StringComparison.Ordinal)) if (text.StartsWith(not_found_prefix, StringComparison.Ordinal))
{ {
ErrorText.Text = "The selected beatmap is not available online."; ErrorText.Text = "The selected beatmap is not available online.";
SelectedItem.Value.MarkInvalid(); CurrentPlaylistItem.Value.MarkInvalid();
} }
else else
{ {

View File

@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{ {
base.LoadComplete(); base.LoadComplete();
SelectedItem.BindValueChanged(_ => updateState()); CurrentPlaylistItem.BindValueChanged(_ => updateState());
} }
protected override void OnRoomUpdated() protected override void OnRoomUpdated()
@ -111,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
bool enableButton = bool enableButton =
Room?.State == MultiplayerRoomState.Open Room?.State == MultiplayerRoomState.Open
&& SelectedItem.Value?.ID == Room.Settings.PlaylistItemId && CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId
&& !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired && !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired
&& !operationInProgress.Value; && !operationInProgress.Value;

View File

@ -52,14 +52,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
queueList = new MultiplayerQueueList queueList = new MultiplayerQueueList
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
SelectedItem = { BindTarget = SelectedItem }, SelectedItem = { BindTarget = CurrentPlaylistItem },
RequestEdit = item => RequestEdit?.Invoke(item) RequestEdit = item => RequestEdit?.Invoke(item)
}, },
historyList = new MultiplayerHistoryList historyList = new MultiplayerHistoryList
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Alpha = 0, Alpha = 0,
SelectedItem = { BindTarget = SelectedItem } SelectedItem = { BindTarget = CurrentPlaylistItem }
} }
} }
} }

View File

@ -32,9 +32,22 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved(typeof(Room))] [Resolved(typeof(Room))]
protected Bindable<MatchType> Type { get; private set; } protected Bindable<MatchType> Type { get; private set; }
/// <summary>
/// The currently selected item in the <see cref="RoomSubScreen"/>, or the current item from <see cref="Playlist"/>
/// if this <see cref="OnlinePlayComposite"/> is not within a <see cref="RoomSubScreen"/>.
/// </summary>
[Resolved(typeof(Room))]
protected Bindable<PlaylistItem> CurrentPlaylistItem { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<Room.RoomPlaylistItemStats> PlaylistItemStats { get; private set; }
[Resolved(typeof(Room))] [Resolved(typeof(Room))]
protected BindableList<PlaylistItem> Playlist { get; private set; } protected BindableList<PlaylistItem> Playlist { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<Room.RoomDifficultyRange> DifficultyRange { get; private set; }
[Resolved(typeof(Room))] [Resolved(typeof(Room))]
protected Bindable<RoomCategory> Category { get; private set; } protected Bindable<RoomCategory> Category { get; private set; }
@ -71,12 +84,6 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private IBindable<PlaylistItem> subScreenSelectedItem { get; set; } private IBindable<PlaylistItem> subScreenSelectedItem { get; set; }
/// <summary>
/// The currently selected item in the <see cref="RoomSubScreen"/>, or the current item from <see cref="Playlist"/>
/// if this <see cref="OnlinePlayComposite"/> is not within a <see cref="RoomSubScreen"/>.
/// </summary>
protected readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
@ -85,9 +92,13 @@ namespace osu.Game.Screens.OnlinePlay
Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true); Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true);
} }
protected virtual void UpdateSelectedItem() protected void UpdateSelectedItem()
=> SelectedItem.Value = RoomID.Value == null || subScreenSelectedItem == null {
? Playlist.GetCurrentItem() // null room ID means this is a room in the process of being created.
: subScreenSelectedItem.Value; if (RoomID.Value == null)
CurrentPlaylistItem.Value = Playlist.GetCurrentItem();
else if (subScreenSelectedItem != null)
CurrentPlaylistItem.Value = subScreenSelectedItem.Value;
}
} }
} }

View File

@ -43,6 +43,11 @@ namespace osu.Game.Tests.Visual.OnlinePlay
if (ruleset != null) if (ruleset != null)
{ {
room.PlaylistItemStats.Value = new Room.RoomPlaylistItemStats
{
RulesetIDs = new[] { ruleset.OnlineID },
};
room.Playlist.Add(new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) room.Playlist.Add(new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() })
{ {
RulesetID = ruleset.OnlineID, RulesetID = ruleset.OnlineID,