1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-19 07:51:17 +08:00

Merge pull request #34419 from bdach/online-status-stuff

Update beatmap online statuses when the set is selected in song select
This commit is contained in:
Dean Herbert
2025-08-04 22:48:12 +09:00
committed by GitHub
Unverified
3 changed files with 65 additions and 29 deletions
@@ -43,6 +43,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
private APIBeatmapSet? currentOnlineSet;
[Cached]
private RealmPopulatingOnlineLookupSource lookupSource = new RealmPopulatingOnlineLookupSource();
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
@@ -55,6 +58,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddRange(new Drawable[]
{
lookupSource,
new Container
{
RelativeSizeAxes = Axes.Both,
+34 -16
View File
@@ -8,18 +8,19 @@ using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
@@ -69,10 +70,14 @@ namespace osu.Game.Screens.SelectV2
private LocalisationManager localisation { get; set; } = null!;
[Resolved]
private IAPIProvider api { get; set; } = null!;
private RealmPopulatingOnlineLookupSource onlineLookupSource { get; set; } = null!;
[Resolved]
private RealmAccess realm { get; set; } = null!;
private APIBeatmapSet? currentOnlineBeatmapSet;
private GetBeatmapSetRequest? currentRequest;
private CancellationTokenSource? cancellationTokenSource;
private Task<APIBeatmapSet?>? currentFetchTask;
private FillFlowContainer statisticsFlow = null!;
@@ -288,28 +293,27 @@ namespace osu.Game.Screens.SelectV2
{
var beatmapSetInfo = working.Value.BeatmapSetInfo;
currentRequest?.Cancel();
currentRequest = null;
cancellationTokenSource?.Cancel();
currentOnlineBeatmapSet = null;
if (beatmapSetInfo.OnlineID >= 1)
{
// todo: consider introducing a BeatmapSetLookupCache for caching benefits.
currentRequest = new GetBeatmapSetRequest(beatmapSetInfo.OnlineID);
currentRequest.Failure += _ => updateOnlineDisplay();
currentRequest.Success += s =>
cancellationTokenSource = new CancellationTokenSource();
currentFetchTask = onlineLookupSource.GetBeatmapSetAsync(beatmapSetInfo.OnlineID);
currentFetchTask.ContinueWith(t =>
{
currentOnlineBeatmapSet = s;
updateOnlineDisplay();
};
api.Queue(currentRequest);
if (t.IsCompletedSuccessfully)
currentOnlineBeatmapSet = t.GetResultSafely();
if (t.Exception != null)
Logger.Log($"Error when fetching online beatmap set: {t.Exception}", LoggingTarget.Network);
Scheduler.AddOnce(updateOnlineDisplay);
});
}
}
private void updateOnlineDisplay()
{
if (currentRequest?.CompletionState == APIRequestCompletionState.Waiting)
if (currentFetchTask?.IsCompleted == false)
{
playCount.Value = null;
favouriteButton.SetLoading();
@@ -319,6 +323,20 @@ namespace osu.Game.Screens.SelectV2
var onlineBeatmap = currentOnlineBeatmapSet?.Beatmaps.SingleOrDefault(b => b.OnlineID == working.Value.BeatmapInfo.OnlineID);
playCount.Value = new StatisticPlayCount.Data(onlineBeatmap?.PlayCount ?? -1, onlineBeatmap?.UserPlayCount ?? -1);
favouriteButton.SetBeatmapSet(currentOnlineBeatmapSet);
// the online fetch may have also updated the beatmap's status.
// this needs to be checked against the *local* beatmap model rather than the online one, because it's not known here whether the status change has occurred or not
// (think scenarios like the beatmap being locally modified).
// it also has to be handled explicitly like this because the working beatmap's `BeatmapInfo` will not receive these updates due to being detached
// (and because of https://github.com/ppy/osu/blob/4b73afd1957a9161e2956fc4191c8114d9958372/osu.Game/Screens/SelectV2/SongSelect.cs#L487-L488
// which prevents working beatmap refetches caused by changes to the realm model of perceived low importance).
var status = realm.Run(r =>
{
var refetchedBeatmap = r.Find<BeatmapInfo>(working.Value.BeatmapInfo.ID);
return refetchedBeatmap?.Status;
});
if (status != null)
statusPill.Status = status.Value;
}
}
}
@@ -13,7 +13,6 @@ using osu.Game.Extensions;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using Realms;
namespace osu.Game.Screens.SelectV2
{
@@ -25,6 +24,7 @@ namespace osu.Game.Screens.SelectV2
/// This component is designed to locally persist potentially-volatile online information such as:
/// <list type="bullet">
/// <item>user tags assigned to difficulties of a beatmap,</item>
/// <item>the beatmap's <see cref="BeatmapInfo.Status"/>,</item>
/// <item>guest mappers assigned to difficulties of a beatmap,</item>
/// <item>the local user's best score on a given beatmap.</item>
/// </list>
@@ -54,20 +54,34 @@ namespace osu.Game.Screens.SelectV2
var onlineBeatmaps = onlineBeatmapSet.Beatmaps.ToDictionary(b => b.OnlineID);
realm.Write(r =>
{
foreach (var dbBeatmap in r.All<BeatmapInfo>().Filter($@"{nameof(BeatmapInfo.BeatmapSet)}.{nameof(BeatmapSetInfo.OnlineID)} == $0", id))
var beatmapSet = r.All<BeatmapSetInfo>().Where(b => b.OnlineID == id);
foreach (var dbBeatmapSet in beatmapSet)
{
if (onlineBeatmaps.TryGetValue(dbBeatmap.OnlineID, out var onlineBeatmap))
dbBeatmapSet.Status = onlineBeatmapSet.Status;
foreach (var dbBeatmap in dbBeatmapSet.Beatmaps)
{
string[] userTagsArray = onlineBeatmap.TopTags?
.Select(t => (topTag: t, relatedTag: tagsById.GetValueOrDefault(t.TagId)))
.Where(t => t.relatedTag != null)
// see https://github.com/ppy/osu-web/blob/bb3bd2e7c6f84f26066df5ea20a81c77ec9bb60a/resources/js/beatmapsets-show/controller.ts#L103-L106 for sort criteria
.OrderByDescending(t => t.topTag.VoteCount)
.ThenBy(t => t.relatedTag!.Name)
.Select(t => t.relatedTag!.Name)
.ToArray() ?? [];
dbBeatmap.Metadata.UserTags.Clear();
dbBeatmap.Metadata.UserTags.AddRange(userTagsArray);
if (onlineBeatmaps.TryGetValue(dbBeatmap.OnlineID, out var onlineBeatmap))
{
// compare `BeatmapUpdaterMetadataLookup`
dbBeatmap.OnlineMD5Hash = onlineBeatmap.MD5Hash;
dbBeatmap.LastOnlineUpdate = onlineBeatmap.LastUpdated;
if (dbBeatmap.MatchesOnlineVersion)
dbBeatmap.Status = onlineBeatmap.Status;
string[] userTagsArray = onlineBeatmap.TopTags?
.Select(t => (topTag: t, relatedTag: tagsById.GetValueOrDefault(t.TagId)))
.Where(t => t.relatedTag != null)
// see https://github.com/ppy/osu-web/blob/bb3bd2e7c6f84f26066df5ea20a81c77ec9bb60a/resources/js/beatmapsets-show/controller.ts#L103-L106 for sort criteria
.OrderByDescending(t => t.topTag.VoteCount)
.ThenBy(t => t.relatedTag!.Name)
.Select(t => t.relatedTag!.Name)
.ToArray() ?? [];
dbBeatmap.Metadata.UserTags.Clear();
dbBeatmap.Metadata.UserTags.AddRange(userTagsArray);
}
}
}
});