diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs
index eeb814b4cb..0d430f4903 100644
--- a/osu.Game/Online/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Online/Leaderboards/Leaderboard.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Threading;
using JetBrains.Annotations;
@@ -13,7 +14,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.Threading;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
@@ -44,10 +44,9 @@ namespace osu.Game.Online.Leaderboards
private readonly LoadingSpinner loading;
- private CancellationTokenSource showScoresCancellationSource;
+ private CancellationTokenSource currentFetchCancellationSource;
- private APIRequest getScoresRequest;
- private ScheduledDelegate getScoresRequestCallback;
+ private APIRequest fetchScoresRequest;
[Resolved(CanBeNull = true)]
private IAPIProvider api { get; set; }
@@ -62,7 +61,6 @@ namespace osu.Game.Online.Leaderboards
protected set
{
scores = value;
-
updateScoresDrawables();
}
}
@@ -177,9 +175,10 @@ namespace osu.Game.Online.Leaderboards
///
/// Performs a fetch/refresh of scores to be displayed.
///
+ ///
/// An responsible for the fetch operation. This will be queued and performed automatically.
[CanBeNull]
- protected abstract APIRequest FetchScores();
+ protected abstract APIRequest FetchScores(CancellationToken cancellationToken);
protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index);
@@ -187,37 +186,36 @@ namespace osu.Game.Online.Leaderboards
private void refetchScores()
{
- cancelPendingWork();
+ Reset();
PlaceholderState = PlaceholderState.Retrieving;
loading.Show();
- getScoresRequest = FetchScores();
+ currentFetchCancellationSource = new CancellationTokenSource();
- if (getScoresRequest == null)
+ fetchScoresRequest = FetchScores(currentFetchCancellationSource.Token);
+
+ if (fetchScoresRequest == null)
return;
- getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() =>
+ fetchScoresRequest.Failure += e => Schedule(() =>
{
- if (e is OperationCanceledException)
+ if (e is OperationCanceledException || currentFetchCancellationSource.IsCancellationRequested)
return;
PlaceholderState = PlaceholderState.NetworkFailure;
});
- api?.Queue(getScoresRequest);
+ api?.Queue(fetchScoresRequest);
}
private void cancelPendingWork()
{
- showScoresCancellationSource?.Cancel();
- showScoresCancellationSource = null;
+ currentFetchCancellationSource?.Cancel();
+ currentFetchCancellationSource = null;
- getScoresRequest?.Cancel();
- getScoresRequest = null;
-
- getScoresRequestCallback?.Cancel();
- getScoresRequestCallback = null;
+ fetchScoresRequest?.Cancel();
+ fetchScoresRequest = null;
}
#region Placeholder handling
@@ -279,8 +277,6 @@ namespace osu.Game.Online.Leaderboards
scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire();
scrollFlow = null;
- showScoresCancellationSource?.Cancel();
-
if (scores?.Any() != true)
{
loading.Hide();
@@ -288,6 +284,8 @@ namespace osu.Game.Online.Leaderboards
return;
}
+ Debug.Assert(!currentFetchCancellationSource.IsCancellationRequested);
+
// ensure placeholder is hidden when displaying scores
PlaceholderState = PlaceholderState.Successful;
@@ -315,7 +313,7 @@ namespace osu.Game.Online.Leaderboards
scrollContainer.ScrollToStart(false);
loading.Hide();
- }, (showScoresCancellationSource = new CancellationTokenSource()).Token);
+ }, currentFetchCancellationSource.Token);
}, false);
private void replacePlaceholder(Placeholder placeholder)
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs
index 5b60f64160..1945899a11 100644
--- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Online.API;
@@ -30,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected override bool IsOnlineScope => true;
- protected override APIRequest FetchScores()
+ protected override APIRequest FetchScores(CancellationToken cancellationToken)
{
if (roomId.Value == null)
return null;
@@ -39,6 +40,9 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
req.Success += r =>
{
+ if (cancellationToken.IsCancellationRequested)
+ return;
+
Scores = r.Leaderboard;
TopScore = r.UserScore;
};
diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
index 898b894541..388395c9f9 100644
--- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
@@ -108,13 +108,8 @@ namespace osu.Game.Screens.Select.Leaderboards
protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local;
- private CancellationTokenSource loadCancellationSource;
-
- protected override APIRequest FetchScores()
+ protected override APIRequest FetchScores(CancellationToken cancellationToken)
{
- loadCancellationSource?.Cancel();
- loadCancellationSource = new CancellationTokenSource();
-
var fetchBeatmapInfo = BeatmapInfo;
if (fetchBeatmapInfo == null)
@@ -125,7 +120,7 @@ namespace osu.Game.Screens.Select.Leaderboards
if (Scope == BeatmapLeaderboardScope.Local)
{
- subscribeToLocalScores();
+ subscribeToLocalScores(cancellationToken);
return null;
}
@@ -160,10 +155,10 @@ namespace osu.Game.Screens.Select.Leaderboards
req.Success += r =>
{
- scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), loadCancellationSource.Token)
+ scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken)
.ContinueWith(task => Schedule(() =>
{
- if (loadCancellationSource.IsCancellationRequested)
+ if (cancellationToken.IsCancellationRequested)
return;
Scores = task.GetResultSafely();
@@ -184,7 +179,7 @@ namespace osu.Game.Screens.Select.Leaderboards
Action = () => ScoreSelected?.Invoke(model)
};
- private void subscribeToLocalScores()
+ private void subscribeToLocalScores(CancellationToken cancellationToken)
{
scoreSubscription?.Dispose();
scoreSubscription = null;
@@ -197,35 +192,35 @@ namespace osu.Game.Screens.Select.Leaderboards
+ $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1"
+ $" AND {nameof(ScoreInfo.DeletePending)} == false"
, beatmapInfo.ID, ruleset.Value.ShortName), localScoresChanged);
- }
- private void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception)
- {
- if (IsOnlineScope)
- return;
-
- var scores = sender.AsEnumerable();
-
- if (filterMods && !mods.Value.Any())
+ void localScoresChanged(IRealmCollection sender, ChangeSet changes, Exception exception)
{
- // we need to filter out all scores that have any mods to get all local nomod scores
- scores = scores.Where(s => !s.Mods.Any());
- }
- else if (filterMods)
- {
- // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters)
- // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself
- var selectedMods = mods.Value.Select(m => m.Acronym);
- scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym)));
- }
+ if (IsOnlineScope || cancellationToken.IsCancellationRequested)
+ return;
- scores = scores.Detach();
+ var scores = sender.AsEnumerable();
- scoreManager.OrderByTotalScoreAsync(scores.ToArray(), loadCancellationSource.Token)
- .ContinueWith(ordered =>
- {
- Scores = ordered.GetResultSafely();
- }, TaskContinuationOptions.OnlyOnRanToCompletion);
+ if (filterMods && !mods.Value.Any())
+ {
+ // we need to filter out all scores that have any mods to get all local nomod scores
+ scores = scores.Where(s => !s.Mods.Any());
+ }
+ else if (filterMods)
+ {
+ // otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters)
+ // we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself
+ var selectedMods = mods.Value.Select(m => m.Acronym);
+ scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym)));
+ }
+
+ scores = scores.Detach();
+
+ scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken)
+ .ContinueWith(ordered =>
+ {
+ Scores = ordered.GetResultSafely();
+ }, TaskContinuationOptions.OnlyOnRanToCompletion);
+ }
}
protected override void Dispose(bool isDisposing)