1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-05 10:33:22 +08:00

Decouple game-wide ruleset bindable and refactor LocalUserStatisticsProvider

This also throws away the logic of updating
`API.LocalUser.Value.Statistics`. Components should rely on
`LocalUserStatisticsProvider` instead for proper behaviour and ability
to update on statistics updates.
This commit is contained in:
Salman Alshamrani 2024-11-17 18:13:37 -05:00
parent 6c8a900dcc
commit 4a628287e2
16 changed files with 188 additions and 158 deletions

View File

@ -69,7 +69,7 @@ namespace osu.Desktop
};
private IBindable<APIUser>? user;
private IBindable<UserStatistics?>? localStatistics;
private IBindable<UserStatisticsUpdate>? statisticsUpdate;
[BackgroundDependencyLoader]
private void load()
@ -123,8 +123,8 @@ namespace osu.Desktop
activity.BindValueChanged(_ => schedulePresenceUpdate());
privacyMode.BindValueChanged(_ => schedulePresenceUpdate());
localStatistics = statisticsProvider.Statistics.GetBoundCopy();
localStatistics.BindValueChanged(_ => schedulePresenceUpdate());
statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy();
statisticsUpdate.BindValueChanged(_ => schedulePresenceUpdate());
multiplayerClient.RoomUpdated += onRoomUpdated;
}
@ -167,7 +167,7 @@ namespace osu.Desktop
private void updatePresence(bool hideIdentifiableInformation)
{
if (user == null || localStatistics == null)
if (user == null)
return;
// user activity
@ -237,7 +237,10 @@ namespace osu.Desktop
if (privacyMode.Value == DiscordRichPresenceMode.Limited)
presence.Assets.LargeImageText = string.Empty;
else
presence.Assets.LargeImageText = $"{user.Value.Username}" + (localStatistics.Value?.GlobalRank > 0 ? $" (rank #{localStatistics.Value?.GlobalRank:N0})" : string.Empty);
{
var statistics = statisticsProvider.GetStatisticsFor(ruleset.Value);
presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics?.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty);
}
// small image
presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom";

View File

@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("Gain", () =>
{
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
new ScoreInfo(),
new UserStatistics
{
@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("Loss", () =>
{
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
new ScoreInfo(),
new UserStatistics
{
@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("Tiny increase in PP", () =>
{
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
new ScoreInfo(),
new UserStatistics
{
@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("No change 1", () =>
{
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
new ScoreInfo(),
new UserStatistics
{
@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("Was null", () =>
{
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
new ScoreInfo(),
new UserStatistics
{
@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("Became null", () =>
{
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
transientUpdateDisplay.LatestUpdate.Value = new UserStatisticsUpdate(
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
new ScoreInfo(),
new UserStatistics
{

View File

@ -3,14 +3,15 @@
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.Containers;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
@ -34,7 +35,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("setup provider", () =>
{
OsuSpriteText text;
OsuTextFlowContainer text;
((DummyAPIAccess)API).HandleRequest = r =>
{
@ -59,17 +60,31 @@ namespace osu.Game.Tests.Visual.Online
Clear();
Add(statisticsProvider = new LocalUserStatisticsProvider());
Add(text = new OsuSpriteText
Add(text = new OsuTextFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
statisticsProvider.Statistics.BindValueChanged(s =>
statisticsProvider.StatisticsUpdate.BindValueChanged(s =>
{
text.Text = s.NewValue == null
? "Statistics: (null)"
: $"Statistics: (total score: {s.NewValue.TotalScore:N0})";
text.Clear();
foreach (var ruleset in Dependencies.Get<RulesetStore>().AvailableRulesets)
{
text.AddText(statisticsProvider.GetStatisticsFor(ruleset) is UserStatistics statistics
? $"{ruleset.Name} statistics: (total score: {statistics.TotalScore})"
: $"{ruleset.Name} statistics: (null)");
text.NewLine();
}
if (s.NewValue == null)
text.AddText("latest update: (null)");
else
{
text.AddText($"latest update: {s.NewValue.Ruleset}"
+ $" ({(s.NewValue.OldStatistics?.TotalScore.ToString() ?? "null")} -> {s.NewValue.NewStatistics.TotalScore})");
}
});
Ruleset.Value = new OsuRuleset().RulesetInfo;
@ -79,19 +94,10 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestInitialStatistics()
{
AddAssert("initial statistics populated", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(4_000_000));
}
[Test]
public void TestRulesetChanges()
{
AddAssert("statistics from osu", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(4_000_000));
AddStep("change ruleset to taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
AddAssert("statistics from taiko", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(3_000_000));
AddStep("change ruleset to catch", () => Ruleset.Value = new CatchRuleset().RulesetInfo);
AddAssert("statistics from catch", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(2_000_000));
AddStep("change ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo);
AddAssert("statistics from mania", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(1_000_000));
AddAssert("osu statistics populated", () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(4_000_000));
AddAssert("taiko statistics populated", () => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(3_000_000));
AddAssert("catch statistics populated", () => statisticsProvider.GetStatisticsFor(new CatchRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(2_000_000));
AddAssert("mania statistics populated", () => statisticsProvider.GetStatisticsFor(new ManiaRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(1_000_000));
}
[Test]
@ -105,18 +111,44 @@ namespace osu.Game.Tests.Visual.Online
serverSideStatistics[(1000, "taiko")] = new UserStatistics { TotalScore = 6_000_000 };
});
AddAssert("statistics matches user 1001 from osu", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(4_000_000));
AddAssert("statistics matches user 1001 in osu",
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
() => Is.EqualTo(4_000_000));
AddStep("change ruleset to taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
AddAssert("statistics matches user 1001 from taiko", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(3_000_000));
AddAssert("statistics matches user 1001 in taiko",
() => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore,
() => Is.EqualTo(3_000_000));
AddStep("change ruleset to osu", () => Ruleset.Value = new OsuRuleset().RulesetInfo);
setUser(1000, false);
AddAssert("statistics matches user 1000 from osu", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(5_000_000));
AddAssert("statistics matches user 1000 in osu",
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
() => Is.EqualTo(5_000_000));
AddStep("change ruleset to osu", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
AddAssert("statistics matches user 1000 from taiko", () => statisticsProvider.Statistics.Value.AsNonNull().TotalScore, () => Is.EqualTo(6_000_000));
AddAssert("statistics matches user 1000 in taiko",
() => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore,
() => Is.EqualTo(6_000_000));
}
[Test]
public void TestRefetchStatistics()
{
setUser(1001);
AddStep("update statistics server side",
() => serverSideStatistics[(1001, "osu")] = new UserStatistics { TotalScore = 9_000_000 });
AddAssert("statistics match old score",
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
() => Is.EqualTo(4_000_000));
AddStep("request refetch", () => statisticsProvider.RefetchStatistics(new OsuRuleset().RulesetInfo));
AddUntilStep("statistics update raised",
() => statisticsProvider.StatisticsUpdate.Value.NewStatistics.TotalScore,
() => Is.EqualTo(9_000_000));
AddAssert("statistics match new score",
() => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
() => Is.EqualTo(9_000_000));
}
private UserStatistics tryGetStatistics(int userId, string rulesetName)

View File

@ -34,8 +34,8 @@ namespace osu.Game.Tests.Visual.Online
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
[Cached]
private readonly LocalUserStatisticsProvider statisticsProvider = new LocalUserStatisticsProvider();
[Cached(typeof(LocalUserStatisticsProvider))]
private readonly TestUserStatisticsProvider statisticsProvider = new TestUserStatisticsProvider();
[Resolved]
private IRulesetStore rulesetStore { get; set; }
@ -206,5 +206,11 @@ namespace osu.Game.Tests.Visual.Online
public new TextFlowContainer LastVisitMessage => base.LastVisitMessage;
}
private partial class TestUserStatisticsProvider : LocalUserStatisticsProvider
{
public new void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset)
=> base.UpdateStatistics(newStatistics, ruleset);
}
}
}

View File

@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Online
var ruleset = new OsuRuleset().RulesetInfo;
UserStatisticsUpdate? update = null;
ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
feignScoreProcessing(userId, ruleset, 5_000_000);
@ -149,7 +149,7 @@ namespace osu.Game.Tests.Visual.Online
// note ordering - in this test processing completes *before* the registration is added.
feignScoreProcessing(userId, ruleset, 5_000_000);
UserStatisticsUpdate? update = null;
ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online
long scoreId = getScoreId();
var ruleset = new OsuRuleset().RulesetInfo;
UserStatisticsUpdate? update = null;
ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
feignScoreProcessing(userId, ruleset, 5_000_000);
@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.Online
long scoreId = getScoreId();
var ruleset = new OsuRuleset().RulesetInfo;
UserStatisticsUpdate? update = null;
ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
feignScoreProcessing(userId, ruleset, 5_000_000);
@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Online
long scoreId = getScoreId();
var ruleset = new OsuRuleset().RulesetInfo;
UserStatisticsUpdate? update = null;
ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
feignScoreProcessing(userId, ruleset, 5_000_000);
@ -244,7 +244,7 @@ namespace osu.Game.Tests.Visual.Online
feignScoreProcessing(userId, ruleset, 6_000_000);
UserStatisticsUpdate? update = null;
ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(secondScoreId, ruleset, receivedUpdate => update = receivedUpdate);
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, secondScoreId));
@ -262,15 +262,14 @@ namespace osu.Game.Tests.Visual.Online
var ruleset = new OsuRuleset().RulesetInfo;
UserStatisticsUpdate? update = null;
ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
feignScoreProcessing(userId, ruleset, 5_000_000);
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
AddUntilStep("update received", () => update != null);
AddAssert("local user values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000));
AddAssert("statistics values are correct", () => statisticsProvider.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000));
AddAssert("statistics values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000));
}
private int nextUserId = 2000;
@ -292,7 +291,7 @@ namespace osu.Game.Tests.Visual.Online
});
}
private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action<UserStatisticsUpdate> onUpdateReady) =>
private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action<ScoreBasedUserStatisticsUpdate> onUpdateReady) =>
AddStep("register for updates", () =>
{
watcher.RegisterForStatisticsUpdateAfter(

View File

@ -112,6 +112,6 @@ namespace osu.Game.Tests.Visual.Ranking
});
private void displayUpdate(UserStatistics before, UserStatistics after) =>
AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new UserStatisticsUpdate(new ScoreInfo(), before, after));
AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after));
}
}

View File

@ -91,12 +91,12 @@ namespace osu.Game.Tests.Visual.Ranking
UserStatisticsWatcher userStatisticsWatcher = null!;
ScoreInfo score = null!;
AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher()));
AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher(new LocalUserStatisticsProvider())));
AddStep("set user statistics update", () =>
{
score = TestResources.CreateTestScoreInfo();
score.OnlineID = 1234;
((Bindable<UserStatisticsUpdate>)userStatisticsWatcher.LatestUpdate).Value = new UserStatisticsUpdate(score,
((Bindable<ScoreBasedUserStatisticsUpdate>)userStatisticsWatcher.LatestUpdate).Value = new ScoreBasedUserStatisticsUpdate(score,
new UserStatistics
{
Level = new UserStatistics.LevelInfo
@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Ranking
Score = { Value = score },
DisplayedUserStatisticsUpdate =
{
Value = new UserStatisticsUpdate(score, new UserStatistics
Value = new ScoreBasedUserStatisticsUpdate(score, new UserStatistics
{
Level = new UserStatistics.LevelInfo
{

View File

@ -223,12 +223,14 @@ namespace osu.Game.Online.API.Requests.Responses
/// <summary>
/// User statistics for the requested ruleset (in the case of a <see cref="GetUserRequest"/> or <see cref="GetFriendsRequest"/> response).
/// Otherwise empty.
/// </summary>
/// <remarks>
/// This returns null when accessed from <see cref="IAPIProvider.LocalUser"/>. Use <see cref="LocalUserStatisticsProvider"/> instead.
/// </remarks>
[JsonProperty(@"statistics")]
public UserStatistics Statistics
{
get => statistics ??= new UserStatistics();
get => statistics;
set
{
if (statistics != null)
@ -242,7 +244,11 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"rank_history")]
private APIRankHistory rankHistory
{
set => Statistics.RankHistory = value;
set
{
statistics ??= new UserStatistics();
statistics.RankHistory = value;
}
}
[JsonProperty(@"active_tournament_banners")]

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -18,84 +19,63 @@ namespace osu.Game.Online
/// </summary>
public partial class LocalUserStatisticsProvider : Component
{
private readonly Bindable<UserStatisticsUpdate> statisticsUpdate = new Bindable<UserStatisticsUpdate>();
/// <summary>
/// A bindable communicating updates to the local user's statistics on any ruleset.
/// This does not guarantee the presence of old statistics, as it is invoked on initial population of statistics.
/// </summary>
public IBindable<UserStatisticsUpdate> StatisticsUpdate => statisticsUpdate;
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
private RulesetStore rulesets { get; set; } = null!;
[Resolved]
private IAPIProvider api { get; set; } = null!;
private readonly Dictionary<string, UserStatistics> statisticsCache = new Dictionary<string, UserStatistics>();
private readonly Dictionary<string, GetUserRequest> statisticsRequests = new Dictionary<string, GetUserRequest>();
/// <summary>
/// The statistics of the local user for the game-wide selected ruleset.
/// Returns the <see cref="UserStatistics"/> currently available for the given ruleset.
/// This may return null if the requested statistics has not been fetched before yet.
/// </summary>
public IBindable<UserStatistics?> Statistics => statistics;
private readonly Bindable<UserStatistics?> statistics = new Bindable<UserStatistics?>();
private readonly Dictionary<string, UserStatistics> allStatistics = new Dictionary<string, UserStatistics>();
/// <param name="ruleset">The ruleset to return the corresponding <see cref="UserStatistics"/> for.</param>
public UserStatistics? GetStatisticsFor(RulesetInfo ruleset) => statisticsCache.GetValueOrDefault(ruleset.ShortName);
protected override void LoadComplete()
{
base.LoadComplete();
statistics.BindValueChanged(v =>
{
if (api.LocalUser.Value != null && v.NewValue != null)
api.LocalUser.Value.Statistics = v.NewValue;
});
ruleset.BindValueChanged(_ => updateStatisticsBindable());
api.LocalUser.BindValueChanged(_ =>
{
allStatistics.Clear();
updateStatisticsBindable();
}, true);
api.LocalUser.BindValueChanged(_ => initialiseStatistics(), true);
}
private GetUserRequest? currentRequest;
private void updateStatisticsBindable() => Schedule(() =>
private void initialiseStatistics()
{
statistics.Value = null;
statisticsCache.Clear();
if (api.LocalUser.Value == null || api.LocalUser.Value.OnlineID <= 1 || !ruleset.Value.IsLegacyRuleset())
{
statistics.Value = new UserStatistics();
return;
}
if (currentRequest?.CompletionState == APIRequestCompletionState.Waiting)
{
currentRequest.Cancel();
currentRequest = null;
}
if (allStatistics.TryGetValue(ruleset.Value.ShortName, out var existing))
statistics.Value = existing;
else
requestStatistics(ruleset.Value);
});
private void requestStatistics(RulesetInfo ruleset)
{
currentRequest = new GetUserRequest(api.LocalUser.Value.OnlineID, ruleset);
currentRequest.Success += u => statistics.Value = allStatistics[ruleset.ShortName] = u.Statistics;
api.Queue(currentRequest);
foreach (var ruleset in rulesets.AvailableRulesets.Where(r => r.IsLegacyRuleset()))
RefetchStatistics(ruleset);
}
/// <summary>
/// Returns the <see cref="UserStatistics"/> currently available for the given ruleset.
/// This may return null if the requested statistics has not been fetched yet.
/// </summary>
/// <param name="ruleset">The ruleset to return the corresponding <see cref="UserStatistics"/> for.</param>
internal UserStatistics? GetStatisticsFor(RulesetInfo ruleset) => allStatistics.GetValueOrDefault(ruleset.ShortName);
internal void UpdateStatistics(UserStatistics statistics, RulesetInfo statisticsRuleset)
public void RefetchStatistics(RulesetInfo ruleset)
{
allStatistics[statisticsRuleset.ShortName] = statistics;
if (statisticsRequests.TryGetValue(ruleset.ShortName, out var previousRequest))
previousRequest.Cancel();
if (statisticsRuleset.ShortName == ruleset.Value.ShortName)
updateStatisticsBindable();
var request = statisticsRequests[ruleset.ShortName] = new GetUserRequest(api.LocalUser.Value.Id, ruleset);
request.Success += u => UpdateStatistics(u.Statistics, ruleset);
api.Queue(request);
}
protected void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset)
{
var oldStatistics = statisticsCache.GetValueOrDefault(ruleset.ShortName);
statisticsRequests.Remove(ruleset.ShortName);
statisticsCache[ruleset.ShortName] = newStatistics;
statisticsUpdate.Value = new UserStatisticsUpdate(ruleset, oldStatistics, newStatistics);
}
}
public record UserStatisticsUpdate(RulesetInfo Ruleset, UserStatistics? OldStatistics, UserStatistics NewStatistics);
}

View File

@ -9,7 +9,7 @@ namespace osu.Game.Online
/// <summary>
/// Contains data about the change in a user's profile statistics after completing a score.
/// </summary>
public class UserStatisticsUpdate
public class ScoreBasedUserStatisticsUpdate
{
/// <summary>
/// The score set by the user that triggered the update.
@ -27,12 +27,12 @@ namespace osu.Game.Online
public UserStatistics After { get; }
/// <summary>
/// Creates a new <see cref="UserStatisticsUpdate"/>.
/// Creates a new <see cref="ScoreBasedUserStatisticsUpdate"/>.
/// </summary>
/// <param name="score">The score set by the user that triggered the update.</param>
/// <param name="before">The user's profile statistics prior to the score being set.</param>
/// <param name="after">The user's profile statistics after the score was set.</param>
public UserStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after)
public ScoreBasedUserStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after)
{
Score = score;
Before = before;

View File

@ -8,10 +8,8 @@ using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Game.Extensions;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Spectator;
using osu.Game.Scoring;
using osu.Game.Users;
namespace osu.Game.Online
{
@ -20,9 +18,12 @@ namespace osu.Game.Online
/// </summary>
public partial class UserStatisticsWatcher : Component
{
private readonly LocalUserStatisticsProvider? statisticsProvider;
public IBindable<UserStatisticsUpdate?> LatestUpdate => latestUpdate;
private readonly Bindable<UserStatisticsUpdate?> latestUpdate = new Bindable<UserStatisticsUpdate?>();
private readonly LocalUserStatisticsProvider statisticsProvider;
public IBindable<ScoreBasedUserStatisticsUpdate?> LatestUpdate => latestUpdate;
private readonly Bindable<ScoreBasedUserStatisticsUpdate?> latestUpdate = new Bindable<ScoreBasedUserStatisticsUpdate?>();
private ScoreInfo? scorePendingUpdate;
[Resolved]
private SpectatorClient spectatorClient { get; set; } = null!;
@ -32,7 +33,7 @@ namespace osu.Game.Online
private readonly Dictionary<long, ScoreInfo> watchedScores = new Dictionary<long, ScoreInfo>();
public UserStatisticsWatcher(LocalUserStatisticsProvider? statisticsProvider = null)
public UserStatisticsWatcher(LocalUserStatisticsProvider statisticsProvider)
{
this.statisticsProvider = statisticsProvider;
}
@ -40,7 +41,9 @@ namespace osu.Game.Online
protected override void LoadComplete()
{
base.LoadComplete();
spectatorClient.OnUserScoreProcessed += userScoreProcessed;
statisticsProvider.StatisticsUpdate.ValueChanged += onStatisticsUpdated;
}
/// <summary>
@ -69,27 +72,20 @@ namespace osu.Game.Online
if (!watchedScores.Remove(scoreId, out var scoreInfo))
return;
requestStatisticsUpdate(userId, scoreInfo);
scorePendingUpdate = scoreInfo;
statisticsProvider.RefetchStatistics(scoreInfo.Ruleset);
}
private void requestStatisticsUpdate(int userId, ScoreInfo scoreInfo)
private void onStatisticsUpdated(ValueChangedEvent<UserStatisticsUpdate> update) => Schedule(() =>
{
var request = new GetUserRequest(userId, scoreInfo.Ruleset);
request.Success += user => Schedule(() => dispatchStatisticsUpdate(scoreInfo, user.Statistics));
api.Queue(request);
}
private void dispatchStatisticsUpdate(ScoreInfo scoreInfo, UserStatistics updatedStatistics)
{
if (statisticsProvider == null)
if (scorePendingUpdate == null || !update.NewValue.Ruleset.Equals(scorePendingUpdate.Ruleset))
return;
var latestRulesetStatistics = statisticsProvider.GetStatisticsFor(scoreInfo.Ruleset);
statisticsProvider.UpdateStatistics(updatedStatistics, scoreInfo.Ruleset);
if (update.NewValue.OldStatistics != null)
latestUpdate.Value = new ScoreBasedUserStatisticsUpdate(scorePendingUpdate, update.NewValue.OldStatistics, update.NewValue.NewStatistics);
if (latestRulesetStatistics != null)
latestUpdate.Value = new UserStatisticsUpdate(scoreInfo, latestRulesetStatistics, updatedStatistics);
}
scorePendingUpdate = null;
});
protected override void Dispose(bool isDisposing)
{

View File

@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar
{
public partial class TransientUserStatisticsUpdateDisplay : CompositeDrawable
{
public Bindable<UserStatisticsUpdate?> LatestUpdate { get; } = new Bindable<UserStatisticsUpdate?>();
public Bindable<ScoreBasedUserStatisticsUpdate?> LatestUpdate { get; } = new Bindable<ScoreBasedUserStatisticsUpdate?>();
private Statistic<int> globalRank = null!;
private Statistic<int> pp = null!;
@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar
};
if (userStatisticsWatcher != null)
((IBindable<UserStatisticsUpdate?>)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate);
((IBindable<ScoreBasedUserStatisticsUpdate?>)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate);
}
protected override void LoadComplete()

View File

@ -14,7 +14,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
{
private const float transition_duration = 300;
public Bindable<UserStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<UserStatisticsUpdate?>();
public Bindable<ScoreBasedUserStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<ScoreBasedUserStatisticsUpdate?>();
private LoadingLayer loadingLayer = null!;
private GridContainer content = null!;
@ -86,7 +86,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
FinishTransforms(true);
}
private void onUpdateReceived(ValueChangedEvent<UserStatisticsUpdate?> update)
private void onUpdateReceived(ValueChangedEvent<ScoreBasedUserStatisticsUpdate?> update)
{
if (update.NewValue == null)
{

View File

@ -19,7 +19,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
{
public abstract partial class RankingChangeRow<T> : CompositeDrawable
{
public Bindable<UserStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<UserStatisticsUpdate?>();
public Bindable<ScoreBasedUserStatisticsUpdate?> StatisticsUpdate { get; } = new Bindable<ScoreBasedUserStatisticsUpdate?>();
private readonly Func<UserStatistics, T> accessor;
@ -113,7 +113,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User
StatisticsUpdate.BindValueChanged(onStatisticsUpdate, true);
}
private void onStatisticsUpdate(ValueChangedEvent<UserStatisticsUpdate?> statisticsUpdate)
private void onStatisticsUpdate(ValueChangedEvent<ScoreBasedUserStatisticsUpdate?> statisticsUpdate)
{
var update = statisticsUpdate.NewValue;

View File

@ -18,9 +18,9 @@ namespace osu.Game.Screens.Ranking.Statistics
{
private readonly ScoreInfo achievedScore;
internal readonly Bindable<UserStatisticsUpdate?> DisplayedUserStatisticsUpdate = new Bindable<UserStatisticsUpdate?>();
internal readonly Bindable<ScoreBasedUserStatisticsUpdate?> DisplayedUserStatisticsUpdate = new Bindable<ScoreBasedUserStatisticsUpdate?>();
private IBindable<UserStatisticsUpdate?> latestGlobalStatisticsUpdate = null!;
private IBindable<ScoreBasedUserStatisticsUpdate?> latestGlobalStatisticsUpdate = null!;
public UserStatisticsPanel(ScoreInfo achievedScore)
{

View File

@ -12,6 +12,7 @@ using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osuTK;
namespace osu.Game.Users
@ -29,8 +30,6 @@ namespace osu.Game.Users
private ProfileValueDisplay countryRankDisplay = null!;
private LoadingLayer loadingLayer = null!;
private readonly IBindable<UserStatistics?> statistics = new Bindable<UserStatistics?>();
public UserRankPanel(APIUser user)
: base(user)
{
@ -47,22 +46,31 @@ namespace osu.Game.Users
[Resolved]
private LocalUserStatisticsProvider? statisticsProvider { get; set; }
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
private IBindable<UserStatisticsUpdate> statisticsUpdate = null!;
protected override void LoadComplete()
{
base.LoadComplete();
if (statisticsProvider != null)
{
statistics.BindTo(statisticsProvider.Statistics);
statistics.BindValueChanged(stats =>
{
loadingLayer.State.Value = stats.NewValue == null ? Visibility.Visible : Visibility.Hidden;
globalRankDisplay.Content = stats.NewValue?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-";
countryRankDisplay.Content = stats.NewValue?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-";
}, true);
statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy();
statisticsUpdate.BindValueChanged(_ => updateDisplay(), true);
}
}
private void updateDisplay()
{
var statistics = statisticsProvider?.GetStatisticsFor(ruleset.Value);
loadingLayer.State.Value = statistics == null ? Visibility.Visible : Visibility.Hidden;
globalRankDisplay.Content = statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? "-";
countryRankDisplay.Content = statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? "-";
}
protected override Drawable CreateLayout()
{
FillFlowContainer details;