1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-13 20:33:35 +08:00
Files
osu-lazer/osu.Game/Screens/Select/PanelLocalRankDisplay.cs
T
Dean Herbert 08c02e29b9 Fix song select scrolling performance when user has many beatmaps loaded (#37666)
Also fixes wrong rank showing briefly in some scenarios.

---

I'm quite confused why the overhead is in the post-async-filter
collection access, but it is. It occurs when using `MaxBy` (realm
snapshot creation), but also when calling `.Count` on the collection, or
even just accessing `sender[0]`. I tried everything, and ended up
settling on simplifying the realm part enough that we can do
post-filtering without much sweat or mess.

Before:


https://github.com/user-attachments/assets/59aed895-03ed-4923-9515-9a5426156f7e

After:


https://github.com/user-attachments/assets/9a37f34a-c955-45bf-877f-89f248d8ea72

Tested using [this realm](https://screvillshot.s-ul.eu/YJUJ4SR1).

- Closes https://github.com/ppy/osu/issues/37574.
- Closes https://github.com/ppy/osu/issues/37661.
2026-05-08 13:25:55 +02:00

112 lines
3.3 KiB
C#

// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osuTK;
using Realms;
namespace osu.Game.Screens.Select
{
public partial class PanelLocalRankDisplay : CompositeDrawable
{
private BeatmapInfo? beatmap;
public BeatmapInfo? Beatmap
{
get => beatmap;
set
{
beatmap = value;
if (IsLoaded)
updateSubscription();
}
}
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
[Resolved]
private RealmAccess realm { get; set; } = null!;
[Resolved]
private IAPIProvider api { get; set; } = null!;
private IDisposable? scoreSubscription;
private readonly UpdateableRank updateable;
public bool HasRank => updateable.Rank != null;
public PanelLocalRankDisplay(BeatmapInfo? beatmap = null)
{
AutoSizeAxes = Axes.Both;
InternalChild = updateable = new UpdateableRank(animate: false)
{
Size = new Vector2(40, 20),
Alpha = 0,
};
Beatmap = beatmap;
}
protected override void LoadComplete()
{
base.LoadComplete();
ruleset.BindValueChanged(_ => updateSubscription(), true);
}
private void updateSubscription()
{
scoreSubscription?.Dispose();
setRankFromScore(null);
if (beatmap == null)
return;
scoreSubscription = realm.RegisterForNotifications(r => r.All<ScoreInfo>().Where(s => s.BeatmapHash == beatmap.Hash && !s.DeletePending), localScoresChanged);
}
private void localScoresChanged(IRealmCollection<ScoreInfo> sender, ChangeSet? changes)
{
// This subscription may fire from changes to linked beatmaps, which we don't care about.
// It's currently not possible for a score to be modified after insertion, so we can safely ignore callbacks with only modifications.
if (changes?.HasCollectionChanges() == false)
return;
ScoreInfo? topScore = sender
// doing these post realm filter is most efficient.
.Where(s => s.UserID == api.LocalUser.Value.Id || s.UserID <= 1)
.Where(s => s.Ruleset.ShortName == ruleset.Value.ShortName)
.MaxBy(info => (info.TotalScore, -info.Date.UtcDateTime.Ticks));
setRankFromScore(topScore);
}
private void setRankFromScore(ScoreInfo? topScore)
{
updateable.Rank = topScore?.Rank;
updateable.Alpha = topScore != null ? 1 : 0;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
scoreSubscription?.Dispose();
}
}
}