diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index b1badc6282..e62e53bd02 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -35,6 +35,8 @@ namespace osu.Game.Tests.Visual.Online private Action? handleGetUsersRequest; private Action? handleGetUserRequest; + private IDisposable? subscription; + private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>(); [SetUpSteps] @@ -246,6 +248,26 @@ namespace osu.Game.Tests.Visual.Online AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(6_000_000)); } + [Test] + public void TestStatisticsUpdateNotFiredAfterSubscriptionDisposal() + { + int userId = getUserId(); + setUpUser(userId); + + long scoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; + + SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); + AddStep("unsubscribe", () => subscription!.Dispose()); + + feignScoreProcessing(userId, ruleset, 5_000_000); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); + AddWaitStep("wait a bit", 5); + AddAssert("update not received", () => update == null); + } + private int nextUserId = 2000; private long nextScoreId = 50000; @@ -266,7 +288,7 @@ namespace osu.Game.Tests.Visual.Online } private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) => - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + AddStep("register for updates", () => subscription = watcher.RegisterForStatisticsUpdateAfter( new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) { Ruleset = rulesetInfo, diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 235abde068..46449fea73 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -46,24 +46,30 @@ namespace osu.Game.Online.Solo /// /// The score to listen for the statistics update for. /// The callback to be invoked once the statistics update has been prepared. - public void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) => Schedule(() => + /// An representing the subscription. Disposing it is equivalent to unsubscribing from future notifications. + public IDisposable RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) { - if (!api.IsLoggedIn) - return; - - if (!score.Ruleset.IsLegacyRuleset() || score.OnlineID <= 0) - return; - - var callback = new StatisticsUpdateCallback(score, onUpdateReady); - - if (lastProcessedScoreId == score.OnlineID) + Schedule(() => { - requestStatisticsUpdate(api.LocalUser.Value.Id, callback); - return; - } + if (!api.IsLoggedIn) + return; - callbacks.Add(score.OnlineID, callback); - }); + if (!score.Ruleset.IsLegacyRuleset() || score.OnlineID <= 0) + return; + + var callback = new StatisticsUpdateCallback(score, onUpdateReady); + + if (lastProcessedScoreId == score.OnlineID) + { + requestStatisticsUpdate(api.LocalUser.Value.Id, callback); + return; + } + + callbacks.Add(score.OnlineID, callback); + }); + + return new InvokeOnDisposal(() => Schedule(() => callbacks.Remove(score.OnlineID))); + } private void onUserChanged(APIUser? localUser) => Schedule(() => { diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 94d333c2da..c8920a734d 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Ranking [Resolved] private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } = null!; + private IDisposable? statisticsSubscription; private readonly Bindable statisticsUpdate = new Bindable(); public SoloResultsScreen(ScoreInfo score, bool allowRetry) @@ -44,7 +45,7 @@ namespace osu.Game.Screens.Ranking base.LoadComplete(); if (ShowUserStatistics) - soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); + statisticsSubscription = soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); } protected override StatisticsPanel CreateStatisticsPanel() @@ -75,6 +76,7 @@ namespace osu.Game.Screens.Ranking base.Dispose(isDisposing); getScoreRequest?.Cancel(); + statisticsSubscription?.Dispose(); } } }