diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index c9529d2f5e..c08185ddbe 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -69,7 +69,6 @@ internal partial class DiscordRichPresence : Component }; private IBindable? user; - private IBindable? statisticsUpdate; [BackgroundDependencyLoader] private void load() @@ -123,10 +122,8 @@ protected override void LoadComplete() activity.BindValueChanged(_ => schedulePresenceUpdate()); privacyMode.BindValueChanged(_ => schedulePresenceUpdate()); - statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); - statisticsUpdate.BindValueChanged(_ => schedulePresenceUpdate()); - multiplayerClient.RoomUpdated += onRoomUpdated; + statisticsProvider.StatisticsUpdated += onStatisticsUpdated; } private void onReady(object _, ReadyMessage __) @@ -142,6 +139,8 @@ private void onReady(object _, ReadyMessage __) private void onRoomUpdated() => schedulePresenceUpdate(); + private void onStatisticsUpdated(UserStatisticsUpdate _) => schedulePresenceUpdate(); + private ScheduledDelegate? presenceUpdateDelegate; private void schedulePresenceUpdate() @@ -353,6 +352,9 @@ protected override void Dispose(bool isDisposing) if (multiplayerClient.IsNotNull()) multiplayerClient.RoomUpdated -= onRoomUpdated; + if (statisticsProvider.IsNotNull()) + statisticsProvider.StatisticsUpdated -= onStatisticsUpdated; + client.Dispose(); base.Dispose(isDisposing); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs b/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs index 342d805be4..f24a9333c1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs @@ -66,7 +66,7 @@ public void SetUpSteps() Origin = Anchor.Centre, }); - statisticsProvider.StatisticsUpdate.BindValueChanged(s => + statisticsProvider.StatisticsUpdated += update => { text.Clear(); @@ -78,14 +78,9 @@ public void SetUpSteps() 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})"); - } - }); + text.AddText($"latest update: {update.Ruleset}" + + $" ({(update.OldStatistics?.TotalScore.ToString() ?? "null")} -> {update.NewStatistics.TotalScore})"); + }; Ruleset.Value = new OsuRuleset().RulesetInfo; }); @@ -133,6 +128,8 @@ public void TestUserChanges() [Test] public void TestRefetchStatistics() { + UserStatisticsUpdate? update = null; + setUser(1001); AddStep("update statistics server side", @@ -142,13 +139,22 @@ public void TestRefetchStatistics() () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(4_000_000)); + AddStep("setup event", () => + { + update = null; + statisticsProvider.StatisticsUpdated -= onStatisticsUpdated; + statisticsProvider.StatisticsUpdated += onStatisticsUpdated; + }); + AddStep("request refetch", () => statisticsProvider.RefetchStatistics(new OsuRuleset().RulesetInfo)); AddUntilStep("statistics update raised", - () => statisticsProvider.StatisticsUpdate.Value.NewStatistics.TotalScore, + () => update?.NewStatistics.TotalScore, () => Is.EqualTo(9_000_000)); AddAssert("statistics match new score", () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(9_000_000)); + + void onStatisticsUpdated(UserStatisticsUpdate u) => update = u; } private UserStatistics tryGetStatistics(int userId, string rulesetName) diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index bf81356407..d132b86052 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -9,6 +9,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Game.Online; using osu.Game.Rulesets; @@ -51,8 +52,6 @@ private IEnumerable orderedRulesets } } - private IBindable statisticsUpdate = null!; - public DifficultyRecommender(LocalUserStatisticsProvider statisticsProvider) { this.statisticsProvider = statisticsProvider; @@ -72,10 +71,11 @@ protected override void LoadComplete() { base.LoadComplete(); - statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); - statisticsUpdate.ValueChanged += u => updateMapping(u.NewValue.Ruleset, u.NewValue.NewStatistics); + statisticsProvider.StatisticsUpdated += onStatisticsUpdated; } + private void onStatisticsUpdated(UserStatisticsUpdate update) => updateMapping(update.Ruleset, update.NewStatistics); + private void updateMapping(RulesetInfo ruleset, UserStatistics statistics) { // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 @@ -110,5 +110,13 @@ public BeatmapInfo GetRecommendedBeatmap(IEnumerable beatmaps) return null; } + + protected override void Dispose(bool isDisposing) + { + if (statisticsProvider.IsNotNull()) + statisticsProvider.StatisticsUpdated -= onStatisticsUpdated; + + base.Dispose(isDisposing); + } } } diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs index ea4688a307..a17041c996 100644 --- a/osu.Game/Online/LocalUserStatisticsProvider.cs +++ b/osu.Game/Online/LocalUserStatisticsProvider.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Extensions; using osu.Game.Online.API; @@ -19,13 +19,15 @@ namespace osu.Game.Online /// public partial class LocalUserStatisticsProvider : Component { - private readonly Bindable statisticsUpdate = new Bindable(); - /// - /// 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. + /// Invoked whenever a change occured to the statistics of any ruleset, + /// either due to change in local user (log out and log in) or as a result of score submission. /// - public IBindable StatisticsUpdate => statisticsUpdate; + /// + /// This does not guarantee the presence of the old statistics, + /// specifically in the case of initial population or change in local user. + /// + public event Action? StatisticsUpdated; [Resolved] private RulesetStore rulesets { get; set; } = null!; @@ -73,7 +75,7 @@ protected void UpdateStatistics(UserStatistics newStatistics, RulesetInfo rulese statisticsRequests.Remove(ruleset.ShortName); statisticsCache[ruleset.ShortName] = newStatistics; - statisticsUpdate.Value = new UserStatisticsUpdate(ruleset, oldStatistics, newStatistics); + StatisticsUpdated?.Invoke(new UserStatisticsUpdate(ruleset, oldStatistics, newStatistics)); } } diff --git a/osu.Game/Online/UserStatisticsWatcher.cs b/osu.Game/Online/UserStatisticsWatcher.cs index bd3c4b819f..8ed1ff594d 100644 --- a/osu.Game/Online/UserStatisticsWatcher.cs +++ b/osu.Game/Online/UserStatisticsWatcher.cs @@ -43,7 +43,7 @@ protected override void LoadComplete() base.LoadComplete(); spectatorClient.OnUserScoreProcessed += userScoreProcessed; - statisticsProvider.StatisticsUpdate.ValueChanged += onStatisticsUpdated; + statisticsProvider.StatisticsUpdated += onStatisticsUpdated; } /// @@ -76,13 +76,13 @@ private void userScoreProcessed(int userId, long scoreId) statisticsProvider.RefetchStatistics(scoreInfo.Ruleset); } - private void onStatisticsUpdated(ValueChangedEvent update) => Schedule(() => + private void onStatisticsUpdated(UserStatisticsUpdate update) => Schedule(() => { - if (scorePendingUpdate == null || !update.NewValue.Ruleset.Equals(scorePendingUpdate.Ruleset)) + if (scorePendingUpdate == null || !update.Ruleset.Equals(scorePendingUpdate.Ruleset)) return; - if (update.NewValue.OldStatistics != null) - latestUpdate.Value = new ScoreBasedUserStatisticsUpdate(scorePendingUpdate, update.NewValue.OldStatistics, update.NewValue.NewStatistics); + if (update.OldStatistics != null) + latestUpdate.Value = new ScoreBasedUserStatisticsUpdate(scorePendingUpdate, update.OldStatistics, update.NewStatistics); scorePendingUpdate = null; }); diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index c66dd8ef49..5e3ae172be 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -49,23 +50,20 @@ private void load() [Resolved] private IBindable ruleset { get; set; } = null!; - private IBindable statisticsUpdate = null!; - protected override void LoadComplete() { base.LoadComplete(); if (statisticsProvider != null) - { - statisticsUpdate = statisticsProvider.StatisticsUpdate.GetBoundCopy(); - statisticsUpdate.BindValueChanged(u => - { - if (u.NewValue.Ruleset.Equals(ruleset.Value)) - updateDisplay(); - }); + statisticsProvider.StatisticsUpdated += onStatisticsUpdated; - ruleset.BindValueChanged(_ => updateDisplay(), true); - } + ruleset.BindValueChanged(_ => updateDisplay(), true); + } + + private void onStatisticsUpdated(UserStatisticsUpdate update) + { + if (update.Ruleset.Equals(ruleset.Value)) + updateDisplay(); } private void updateDisplay() @@ -231,5 +229,13 @@ protected override void OnHoverLost(HoverLostEvent e) } protected override Drawable? CreateBackground() => null; + + protected override void Dispose(bool isDisposing) + { + if (statisticsProvider.IsNotNull()) + statisticsProvider.StatisticsUpdated -= onStatisticsUpdated; + + base.Dispose(isDisposing); + } } }