1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-05 09:42:54 +08:00

Introduce LocalUserStatisticsProvider component

This commit is contained in:
Salman Ahmed 2024-02-11 07:37:13 +03:00
parent b0420f7ed9
commit 91fb59ee15
3 changed files with 240 additions and 1 deletions

View File

@ -0,0 +1,141 @@
// 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.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
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.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
public partial class TestSceneLocalUserStatisticsProvider : OsuTestScene
{
private LocalUserStatisticsProvider statisticsProvider = null!;
private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>();
[SetUpSteps]
public void SetUpSteps()
{
AddStep("clear statistics", () => serverSideStatistics.Clear());
setUser(1000);
AddStep("setup provider", () =>
{
OsuSpriteText text;
((DummyAPIAccess)API).HandleRequest = r =>
{
switch (r)
{
case GetUserRequest userRequest:
int userId = int.Parse(userRequest.Lookup);
string rulesetName = userRequest.Ruleset!.ShortName;
var response = new APIUser
{
Id = userId,
Statistics = tryGetStatistics(userId, rulesetName)
};
userRequest.TriggerSuccess(response);
return true;
default:
return false;
}
};
Clear();
Add(statisticsProvider = new LocalUserStatisticsProvider());
Add(text = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
statisticsProvider.Statistics.BindValueChanged(s =>
{
text.Text = s.NewValue == null
? "Statistics: (null)"
: $"Statistics: (total score: {s.NewValue.TotalScore:N0})";
});
Ruleset.Value = new OsuRuleset().RulesetInfo;
});
}
[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));
}
[Test]
public void TestUserChanges()
{
setUser(1001);
AddStep("update statistics for user 1000", () =>
{
serverSideStatistics[(1000, "osu")] = new UserStatistics { TotalScore = 5_000_000 };
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));
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));
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));
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));
}
private UserStatistics tryGetStatistics(int userId, string rulesetName)
=> serverSideStatistics.TryGetValue((userId, rulesetName), out var stats) ? stats : new UserStatistics();
private void setUser(int userId, bool generateStatistics = true)
{
AddStep($"set local user to {userId}", () =>
{
if (generateStatistics)
{
serverSideStatistics[(userId, "osu")] = new UserStatistics { TotalScore = 4_000_000 };
serverSideStatistics[(userId, "taiko")] = new UserStatistics { TotalScore = 3_000_000 };
serverSideStatistics[(userId, "fruits")] = new UserStatistics { TotalScore = 2_000_000 };
serverSideStatistics[(userId, "mania")] = new UserStatistics { TotalScore = 1_000_000 };
}
((DummyAPIAccess)API).LocalUser.Value = new APIUser { Id = userId };
});
}
}
}

View File

@ -0,0 +1,94 @@
// 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.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Extensions;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Users;
namespace osu.Game.Online
{
/// <summary>
/// A component that is responsible for providing the latest statistics of the logged-in user for the game-wide selected ruleset.
/// </summary>
public partial class LocalUserStatisticsProvider : Component
{
/// <summary>
/// The statistics of the logged-in user for the game-wide selected ruleset.
/// </summary>
public IBindable<UserStatistics?> Statistics => statistics;
private readonly Bindable<UserStatistics?> statistics = new Bindable<UserStatistics?>();
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
[Resolved]
private IAPIProvider api { get; set; } = null!;
private readonly Dictionary<string, UserStatistics> allStatistics = new Dictionary<string, UserStatistics>();
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);
}
private GetUserRequest? currentRequest;
private void updateStatisticsBindable() => Schedule(() =>
{
statistics.Value = null;
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);
}
internal void UpdateStatistics(UserStatistics statistics, RulesetInfo statisticsRuleset)
{
allStatistics[statisticsRuleset.ShortName] = statistics;
if (statisticsRuleset.ShortName == ruleset.Value.ShortName)
updateStatisticsBindable();
}
}
}

View File

@ -208,6 +208,7 @@ namespace osu.Game
private MetadataClient metadataClient; private MetadataClient metadataClient;
private SoloStatisticsWatcher soloStatisticsWatcher; private SoloStatisticsWatcher soloStatisticsWatcher;
private LocalUserStatisticsProvider localUserStatisticsProvider;
private RealmAccess realm; private RealmAccess realm;
@ -328,7 +329,9 @@ namespace osu.Game
dependencies.CacheAs(SpectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(SpectatorClient = new OnlineSpectatorClient(endpoints));
dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints));
dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints));
dependencies.CacheAs(soloStatisticsWatcher = new SoloStatisticsWatcher());
dependencies.CacheAs(localUserStatisticsProvider = new LocalUserStatisticsProvider());
dependencies.CacheAs(soloStatisticsWatcher = new SoloStatisticsWatcher(localUserStatisticsProvider));
base.Content.Add(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); base.Content.Add(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient));
@ -371,6 +374,7 @@ namespace osu.Game
base.Content.Add(SpectatorClient); base.Content.Add(SpectatorClient);
base.Content.Add(MultiplayerClient); base.Content.Add(MultiplayerClient);
base.Content.Add(metadataClient); base.Content.Add(metadataClient);
base.Content.Add(localUserStatisticsProvider);
base.Content.Add(soloStatisticsWatcher); base.Content.Add(soloStatisticsWatcher);
base.Content.Add(rulesetConfigCache); base.Content.Add(rulesetConfigCache);