From 47061c0210ad37b80ba5778bf205ca91dcc0b132 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 18:57:30 +0900 Subject: [PATCH 01/75] Trigger refresh on scoring mode change --- .../BeatmapSet/Scores/ScoresContainer.cs | 71 ++++++++++++------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index aff48919b4..bfdb90c36a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -12,8 +12,11 @@ using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Framework.Bindables; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; @@ -27,6 +30,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Bindable ruleset = new Bindable(); private readonly Bindable scope = new Bindable(BeatmapLeaderboardScope.Global); private readonly IBindable user = new Bindable(); + private readonly Bindable scoringMode = new Bindable(); private readonly Box background; private readonly ScoreTable scoreTable; @@ -42,35 +46,20 @@ namespace osu.Game.Overlays.BeatmapSet.Scores [Resolved] private RulesetStore rulesets { get; set; } + [Resolved] + private ScoreManager scoreManager { get; set; } + private GetScoresRequest getScoresRequest; + private APILegacyScores scores; + protected APILegacyScores Scores { - set => Schedule(() => + set { - topScoresContainer.Clear(); - - if (value?.Scores.Any() != true) - { - scoreTable.ClearScores(); - scoreTable.Hide(); - return; - } - - var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); - var topScore = scoreInfos.First(); - - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); - scoreTable.Show(); - - var userScore = value.UserScore; - var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); - - topScoresContainer.Add(new DrawableTopScore(topScore)); - - if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) - topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); - }); + scores = value; + displayScores(value); + } } public ScoresContainer() @@ -166,11 +155,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, OsuConfigManager config) { background.Colour = colourProvider.Background5; user.BindTo(api.LocalUser); + config.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); } protected override void LoadComplete() @@ -183,6 +173,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Beatmap.BindValueChanged(onBeatmapChanged); user.BindValueChanged(onUserChanged, true); + + scoringMode.BindValueChanged(_ => displayScores(scores)); } private void onBeatmapChanged(ValueChangedEvent beatmap) @@ -254,6 +246,35 @@ namespace osu.Game.Overlays.BeatmapSet.Scores api.Queue(getScoresRequest); } + private void displayScores(APILegacyScores newScores) + { + Schedule(() => + { + topScoresContainer.Clear(); + + if (newScores?.Scores.Any() != true) + { + scoreTable.ClearScores(); + scoreTable.Hide(); + return; + } + + var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); + var topScore = scoreInfos.First(); + + scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); + scoreTable.Show(); + + var userScore = newScores.UserScore; + var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); + + topScoresContainer.Add(new DrawableTopScore(topScore)); + + if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) + topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); + }); + } + private bool userIsSupporter => api.IsLoggedIn && api.LocalUser.Value.IsSupporter; } } From b217dd1a658b590625a913f361e596ea8587a82c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:03:16 +0900 Subject: [PATCH 02/75] Order scores by score --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index bfdb90c36a..1e5f7c3f72 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -259,7 +259,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; } - var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToList(); + var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)) + .OrderByDescending(s => scoreManager.GetBindableTotalScore(s).Value) + .ToList(); + var topScore = scoreInfos.First(); scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); From d03950fb370869e18f2e9050b2148a06b0393084 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:33:09 +0900 Subject: [PATCH 03/75] Move score calculation to ScoreManager --- osu.Game/Scoring/ScoreManager.cs | 144 ++++++++++++++----------------- 1 file changed, 67 insertions(+), 77 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 83bcac01ac..f310462d6e 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -34,6 +34,7 @@ namespace osu.Game.Scoring protected override string ImportFromStablePath => Path.Combine("Data", "r"); + private readonly Bindable scoringMode = new Bindable(); private readonly RulesetStore rulesets; private readonly Func beatmaps; @@ -51,6 +52,8 @@ namespace osu.Game.Scoring this.beatmaps = beatmaps; this.difficulties = difficulties; this.configManager = configManager; + + configManager?.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); } protected override ScoreInfo CreateModel(ArchiveReader archive) @@ -113,7 +116,7 @@ namespace osu.Game.Scoring /// The bindable containing the total score. public Bindable GetBindableTotalScore(ScoreInfo score) { - var bindable = new TotalScoreBindable(score, difficulties); + var bindable = new TotalScoreBindable(score, this); configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode); return bindable; } @@ -128,6 +131,63 @@ namespace osu.Game.Scoring /// The bindable containing the formatted total score string. public Bindable GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); + public long GetTotalScore(ScoreInfo score) => GetTotalScoreAsync(score).Result; + + public async Task GetTotalScoreAsync(ScoreInfo score, CancellationToken cancellationToken = default) + { + if (score.Beatmap == null) + return score.TotalScore; + + int beatmapMaxCombo; + double accuracy = score.Accuracy; + + if (score.IsLegacyScore) + { + if (score.RulesetID == 3) + { + // In osu!stable, a full-GREAT score has 100% accuracy in mania. Along with a full combo, the score becomes indistinguishable from a full-PERFECT score. + // To get around this, recalculate accuracy based on the hit statistics. + // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. + double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect); + double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum(); + if (maxBaseScore > 0) + accuracy = baseScore / maxBaseScore; + } + + // This score is guaranteed to be an osu!stable score. + // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. + if (score.Beatmap.MaxCombo != null) + beatmapMaxCombo = score.Beatmap.MaxCombo.Value; + else + { + if (score.Beatmap.ID == 0 || difficulties == null) + { + // We don't have enough information (max combo) to compute the score, so use the provided score. + return score.TotalScore; + } + + // We can compute the max combo locally after the async beatmap difficulty computation. + var difficulty = await difficulties().GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); + beatmapMaxCombo = difficulty.MaxCombo; + } + } + else + { + // This is guaranteed to be a non-legacy score. + // The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values. + beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum(); + } + + if (beatmapMaxCombo == 0) + return 0; + + var ruleset = score.Ruleset.CreateInstance(); + var scoreProcessor = ruleset.CreateScoreProcessor(); + scoreProcessor.Mods.Value = score.Mods; + + return (long)Math.Round(scoreProcessor.GetScore(scoringMode.Value, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + } + /// /// Provides the total score of a . Responds to changes in the currently-selected . /// @@ -136,99 +196,29 @@ namespace osu.Game.Scoring public readonly Bindable ScoringMode = new Bindable(); private readonly ScoreInfo score; - private readonly Func difficulties; + private readonly ScoreManager scoreManager; /// /// Creates a new . /// /// The to provide the total score of. - /// A function to retrieve the . - public TotalScoreBindable(ScoreInfo score, Func difficulties) + /// The . + public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager) { this.score = score; - this.difficulties = difficulties; + this.scoreManager = scoreManager; ScoringMode.BindValueChanged(onScoringModeChanged, true); } - private IBindable difficultyBindable; private CancellationTokenSource difficultyCancellationSource; private void onScoringModeChanged(ValueChangedEvent mode) { difficultyCancellationSource?.Cancel(); - difficultyCancellationSource = null; + difficultyCancellationSource = new CancellationTokenSource(); - if (score.Beatmap == null) - { - Value = score.TotalScore; - return; - } - - int beatmapMaxCombo; - double accuracy = score.Accuracy; - - if (score.IsLegacyScore) - { - if (score.RulesetID == 3) - { - // In osu!stable, a full-GREAT score has 100% accuracy in mania. Along with a full combo, the score becomes indistinguishable from a full-PERFECT score. - // To get around this, recalculate accuracy based on the hit statistics. - // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. - double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect); - double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum(); - if (maxBaseScore > 0) - accuracy = baseScore / maxBaseScore; - } - - // This score is guaranteed to be an osu!stable score. - // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. - if (score.Beatmap.MaxCombo == null) - { - if (score.Beatmap.ID == 0 || difficulties == null) - { - // We don't have enough information (max combo) to compute the score, so use the provided score. - Value = score.TotalScore; - return; - } - - // We can compute the max combo locally after the async beatmap difficulty computation. - difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token); - difficultyBindable.BindValueChanged(d => - { - if (d.NewValue is StarDifficulty diff) - updateScore(diff.MaxCombo, accuracy); - }, true); - - return; - } - - beatmapMaxCombo = score.Beatmap.MaxCombo.Value; - } - else - { - // This is guaranteed to be a non-legacy score. - // The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values. - beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum(); - } - - updateScore(beatmapMaxCombo, accuracy); - } - - private void updateScore(int beatmapMaxCombo, double accuracy) - { - if (beatmapMaxCombo == 0) - { - Value = 0; - return; - } - - var ruleset = score.Ruleset.CreateInstance(); - var scoreProcessor = ruleset.CreateScoreProcessor(); - - scoreProcessor.Mods.Value = score.Mods; - - Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + scoreManager.GetTotalScoreAsync(score, difficultyCancellationSource.Token).ContinueWith(s => Value = s.Result, TaskContinuationOptions.OnlyOnRanToCompletion); } } From 458ce250f01c7d81f05c78d2382d1151726cc82a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:34:12 +0900 Subject: [PATCH 04/75] Use new ScoreManager method in ScoreTable --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 4 ++-- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index a154016824..847df53edc 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -151,8 +151,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Current = scoreManager.GetBindableTotalScoreString(score), - Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) + Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium), + Text = scoreManager.GetTotalScore(score).ToLocalisableString(@"N0"), }, new OsuSpriteText { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 1e5f7c3f72..a0ccb5a393 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -260,7 +260,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)) - .OrderByDescending(s => scoreManager.GetBindableTotalScore(s).Value) + .OrderByDescending(s => scoreManager.GetTotalScore(s)) .ToList(); var topScore = scoreInfos.First(); From 4ebb11472df20b65efc4e0f13cfb4bac6f7a6878 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:34:34 +0900 Subject: [PATCH 05/75] Update Leaderboard to reorder scores based on scoring mode --- osu.Game/Online/Leaderboards/Leaderboard.cs | 8 +++++++- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 4f8b27602b..f3a06994ee 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -13,11 +13,13 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Threading; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Placeholders; +using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -27,6 +29,7 @@ namespace osu.Game.Online.Leaderboards { private const double fade_duration = 300; + private readonly Bindable scoringMode = new Bindable(); private readonly OsuScrollContainer scrollContainer; private readonly Container placeholderContainer; private readonly UserTopScoreContainer topScoreContainer; @@ -246,12 +249,15 @@ namespace osu.Game.Online.Leaderboards private readonly IBindable apiState = new Bindable(); [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager configManager) { if (api != null) apiState.BindTo(api.State); apiState.BindValueChanged(onlineStateChanged, true); + + configManager.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); + scoringMode.BindValueChanged(_ => RefreshScores(), true); } private APIRequest getScoresRequest; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 934b905a1a..206b0e7936 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -198,8 +198,8 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.Numeric.With(size: 23), + Text = scoreManager.GetTotalScore(score).ToLocalisableString(@"N0"), }, RankContainer = new Container { diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index a86a614a05..04e7172519 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -146,7 +146,7 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); } - Scores = scores.OrderByDescending(s => s.TotalScore).ToArray(); + Scores = scores.OrderByDescending(s => scoreManager.GetTotalScore(s)).ToArray(); PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; return null; @@ -182,7 +182,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets))); + scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).OrderByDescending(s => scoreManager.GetTotalScore(s))); TopScore = r.UserScore?.CreateScoreInfo(rulesets); }; From e19d81c88ce21aba8cf19219142a60a9ff356236 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:41:44 +0900 Subject: [PATCH 06/75] Fix potential incorrect ordering --- .../Overlays/BeatmapSet/Scores/ScoresContainer.cs | 1 + .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index a0ccb5a393..91c3dfad65 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -261,6 +261,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)) .OrderByDescending(s => scoreManager.GetTotalScore(s)) + .ThenBy(s => s.OnlineScoreID) .ToList(); var topScore = scoreInfos.First(); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 04e7172519..0276ae8b2a 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -146,7 +146,10 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); } - Scores = scores.OrderByDescending(s => scoreManager.GetTotalScore(s)).ToArray(); + Scores = scores.OrderByDescending(s => scoreManager.GetTotalScore(s)) + .ThenBy(s => s.OnlineScoreID) + .ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; return null; @@ -182,7 +185,11 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).OrderByDescending(s => scoreManager.GetTotalScore(s))); + scoresCallback?.Invoke( + r.Scores.Select(s => s.CreateScoreInfo(rulesets)) + .OrderByDescending(s => scoreManager.GetTotalScore(s)) + .ThenBy(s => s.OnlineScoreID)); + TopScore = r.UserScore?.CreateScoreInfo(rulesets); }; From caa797cbf46ad46d45832a14b709a8b9641fea6f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 30 Aug 2021 19:58:35 +0900 Subject: [PATCH 07/75] Attempt to reorder score panel list --- osu.Game/Screens/Ranking/ScorePanelList.cs | 48 +++++++++++++++------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index e170241ede..c9ddfd27d3 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -5,11 +5,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osuTK; using osuTK.Input; @@ -289,27 +292,42 @@ namespace osu.Game.Screens.Ranking { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); + private readonly Bindable scoringMode = new Bindable(); + + [Resolved] + private ScoreManager scoreManager { get; set; } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager configManager) + { + configManager.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + scoringMode.BindValueChanged(mode => + { + foreach (var c in Children) + SetLayoutPosition(c, scoreManager.GetTotalScore(c.Panel.Score)); + }, true); + } + public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); - private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() - .OrderByDescending(s => s.Panel.Score.TotalScore) - .ThenBy(s => s.Panel.Score.OnlineScoreID); - - protected override int Compare(Drawable x, Drawable y) + public override void Add(ScorePanelTrackingContainer drawable) { - var tX = (ScorePanelTrackingContainer)x; - var tY = (ScorePanelTrackingContainer)y; + Debug.Assert(drawable != null); - int result = tY.Panel.Score.TotalScore.CompareTo(tX.Panel.Score.TotalScore); + base.Add(drawable); - if (result != 0) - return result; - - if (tX.Panel.Score.OnlineScoreID == null || tY.Panel.Score.OnlineScoreID == null) - return base.Compare(x, y); - - return tX.Panel.Score.OnlineScoreID.Value.CompareTo(tY.Panel.Score.OnlineScoreID.Value); + SetLayoutPosition(drawable, scoreManager?.GetTotalScore(drawable.Panel.Score) ?? 0); } + + private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() + .OrderByDescending(GetLayoutPosition) + .ThenBy(s => s.Panel.Score.OnlineScoreID); } private class Scroll : OsuScrollContainer From 90768a86a65db383aa2de2d58756c2a59bc80139 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Aug 2021 17:55:13 +0900 Subject: [PATCH 08/75] Adjust classic scoring as a constant multiple over standardised scoring --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 16f2607bad..3073ec9340 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -226,8 +226,9 @@ namespace osu.Game.Rulesets.Scoring return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; case ScoringMode.Classic: - // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return getBonusScore(statistics) + (accuracyRatio * Math.Max(1, maxCombo) * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); + // This feels very similar to osu!stable scoring (ScoreV1) while maintaining that classic scoring is only a constant multiple of standardised scoring. + // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two systems. + return GetScore(ScoringMode.Standardised, maxCombo, accuracyRatio, comboRatio, statistics) / max_score * Math.Pow(maxCombo, 2) * 25; } } From bfcadcc4ac591b8128e26ff80083c478075e367f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Aug 2021 18:56:26 +0900 Subject: [PATCH 09/75] Revert some changes --- osu.Game/Online/Leaderboards/Leaderboard.cs | 5 -- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../BeatmapSet/Scores/ScoresContainer.cs | 72 ++++++++----------- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 +---- 5 files changed, 32 insertions(+), 66 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index f3a06994ee..1d9d2e6b14 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Placeholders; -using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -29,7 +28,6 @@ namespace osu.Game.Online.Leaderboards { private const double fade_duration = 300; - private readonly Bindable scoringMode = new Bindable(); private readonly OsuScrollContainer scrollContainer; private readonly Container placeholderContainer; private readonly UserTopScoreContainer topScoreContainer; @@ -255,9 +253,6 @@ namespace osu.Game.Online.Leaderboards apiState.BindTo(api.State); apiState.BindValueChanged(onlineStateChanged, true); - - configManager.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); - scoringMode.BindValueChanged(_ => RefreshScores(), true); } private APIRequest getScoresRequest; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 206b0e7936..9a0bc35bb3 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -199,7 +199,7 @@ namespace osu.Game.Online.Leaderboards TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), Font = OsuFont.Numeric.With(size: 23), - Text = scoreManager.GetTotalScore(score).ToLocalisableString(@"N0"), + Current = scoreManager.GetBindableTotalScoreString(score) }, RankContainer = new Container { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 847df53edc..9497ed9d35 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -152,7 +152,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Margin = new MarginPadding { Right = horizontal_inset }, Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium), - Text = scoreManager.GetTotalScore(score).ToLocalisableString(@"N0"), + Current = scoreManager.GetBindableTotalScoreString(score), }, new OsuSpriteText { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 91c3dfad65..1b7e152c07 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -15,7 +15,6 @@ using osu.Framework.Bindables; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; @@ -30,7 +29,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Bindable ruleset = new Bindable(); private readonly Bindable scope = new Bindable(BeatmapLeaderboardScope.Global); private readonly IBindable user = new Bindable(); - private readonly Bindable scoringMode = new Bindable(); private readonly Box background; private readonly ScoreTable scoreTable; @@ -51,15 +49,37 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private GetScoresRequest getScoresRequest; - private APILegacyScores scores; - protected APILegacyScores Scores { - set + set => Schedule(() => { - scores = value; - displayScores(value); - } + topScoresContainer.Clear(); + + if (value?.Scores.Any() != true) + { + scoreTable.ClearScores(); + scoreTable.Hide(); + return; + } + + var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)) + .OrderByDescending(s => scoreManager.GetTotalScore(s)) + .ThenBy(s => s.OnlineScoreID) + .ToList(); + + var topScore = scoreInfos.First(); + + scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); + scoreTable.Show(); + + var userScore = value.UserScore; + var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); + + topScoresContainer.Add(new DrawableTopScore(topScore)); + + if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) + topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); + }); } public ScoresContainer() @@ -160,7 +180,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores background.Colour = colourProvider.Background5; user.BindTo(api.LocalUser); - config.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); } protected override void LoadComplete() @@ -173,8 +192,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Beatmap.BindValueChanged(onBeatmapChanged); user.BindValueChanged(onUserChanged, true); - - scoringMode.BindValueChanged(_ => displayScores(scores)); } private void onBeatmapChanged(ValueChangedEvent beatmap) @@ -246,39 +263,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores api.Queue(getScoresRequest); } - private void displayScores(APILegacyScores newScores) - { - Schedule(() => - { - topScoresContainer.Clear(); - - if (newScores?.Scores.Any() != true) - { - scoreTable.ClearScores(); - scoreTable.Hide(); - return; - } - - var scoreInfos = newScores.Scores.Select(s => s.CreateScoreInfo(rulesets)) - .OrderByDescending(s => scoreManager.GetTotalScore(s)) - .ThenBy(s => s.OnlineScoreID) - .ToList(); - - var topScore = scoreInfos.First(); - - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); - scoreTable.Show(); - - var userScore = newScores.UserScore; - var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); - - topScoresContainer.Add(new DrawableTopScore(topScore)); - - if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) - topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); - }); - } - private bool userIsSupporter => api.IsLoggedIn && api.LocalUser.Value.IsSupporter; } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index c9ddfd27d3..ba90585ab5 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -10,9 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Game.Configuration; using osu.Game.Graphics.Containers; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osuTK; using osuTK.Input; @@ -292,26 +290,15 @@ namespace osu.Game.Screens.Ranking { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); - private readonly Bindable scoringMode = new Bindable(); - [Resolved] private ScoreManager scoreManager { get; set; } - [BackgroundDependencyLoader] - private void load(OsuConfigManager configManager) - { - configManager.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); - } - protected override void LoadComplete() { base.LoadComplete(); - scoringMode.BindValueChanged(mode => - { - foreach (var c in Children) - SetLayoutPosition(c, scoreManager.GetTotalScore(c.Panel.Score)); - }, true); + foreach (var c in Children) + SetLayoutPosition(c, scoreManager.GetTotalScore(c.Panel.Score)); } public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); From cfcf3d7507b8b80b89d082e3027d354bcffa5220 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Aug 2021 20:43:50 +0900 Subject: [PATCH 10/75] Use synchronous total score retrieval for bindable --- osu.Game/Scoring/ScoreManager.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index f310462d6e..8cf1c85956 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -195,9 +195,6 @@ namespace osu.Game.Scoring { public readonly Bindable ScoringMode = new Bindable(); - private readonly ScoreInfo score; - private readonly ScoreManager scoreManager; - /// /// Creates a new . /// @@ -205,20 +202,7 @@ namespace osu.Game.Scoring /// The . public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager) { - this.score = score; - this.scoreManager = scoreManager; - - ScoringMode.BindValueChanged(onScoringModeChanged, true); - } - - private CancellationTokenSource difficultyCancellationSource; - - private void onScoringModeChanged(ValueChangedEvent mode) - { - difficultyCancellationSource?.Cancel(); - difficultyCancellationSource = new CancellationTokenSource(); - - scoreManager.GetTotalScoreAsync(score, difficultyCancellationSource.Token).ContinueWith(s => Value = s.Result, TaskContinuationOptions.OnlyOnRanToCompletion); + ScoringMode.BindValueChanged(_ => Value = scoreManager.GetTotalScore(score), true); } } From fee94236de92815f9d9b29375b56c5b7604ec132 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Aug 2021 21:36:31 +0900 Subject: [PATCH 11/75] Fix update-thread pauses --- .../BeatmapSet/Scores/ScoresContainer.cs | 39 +++++++----- osu.Game/Scoring/ScoreManager.cs | 19 ++++++ osu.Game/Screens/Ranking/ScorePanelList.cs | 62 +++++++++++-------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 30 ++++++--- 4 files changed, 99 insertions(+), 51 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 1b7e152c07..8e9a9eb684 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osuTK; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using osu.Game.Online.API.Requests.Responses; using osu.Game.Beatmaps; using osu.Game.Online.API; @@ -49,36 +51,41 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private GetScoresRequest getScoresRequest; + private CancellationTokenSource loadCancellationSource; + protected APILegacyScores Scores { set => Schedule(() => { + loadCancellationSource?.Cancel(); + loadCancellationSource = new CancellationTokenSource(); + topScoresContainer.Clear(); + scoreTable.ClearScores(); + scoreTable.Hide(); if (value?.Scores.Any() != true) - { - scoreTable.ClearScores(); - scoreTable.Hide(); return; - } - var scoreInfos = value.Scores.Select(s => s.CreateScoreInfo(rulesets)) - .OrderByDescending(s => scoreManager.GetTotalScore(s)) - .ThenBy(s => s.OnlineScoreID) - .ToList(); + scoreManager.GetOrderedScoresAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + .ContinueWith(ordered => Schedule(() => + { + if (loadCancellationSource.IsCancellationRequested) + return; - var topScore = scoreInfos.First(); + var topScore = ordered.Result.First(); - scoreTable.DisplayScores(scoreInfos, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); - scoreTable.Show(); + scoreTable.DisplayScores(ordered.Result, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); + scoreTable.Show(); - var userScore = value.UserScore; - var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); + var userScore = value.UserScore; + var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); - topScoresContainer.Add(new DrawableTopScore(topScore)); + topScoresContainer.Add(new DrawableTopScore(topScore)); - if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) - topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); + if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) + topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); + }), TaskContinuationOptions.OnlyOnRanToCompletion); }); } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 8cf1c85956..71edc65fcc 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -106,6 +106,25 @@ namespace osu.Game.Scoring => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); + public async Task GetOrderedScoresAsync(ScoreInfo[] scores, CancellationToken cancellationToken = default) + { + var difficultyCache = difficulties?.Invoke(); + + if (difficultyCache == null) + return orderByTotalScore(scores); + + // Compute difficulties asynchronously first to prevent blocks on the main thread. + foreach (var s in scores) + { + await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } + + return orderByTotalScore(scores); + + ScoreInfo[] orderByTotalScore(IEnumerable incoming) => incoming.OrderByDescending(GetTotalScore).ThenBy(s => s.OnlineScoreID).ToArray(); + } + /// /// Retrieves a bindable that represents the total score of a . /// diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index ba90585ab5..e319e824a1 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -61,6 +62,10 @@ namespace osu.Game.Screens.Ranking public readonly Bindable SelectedScore = new Bindable(); + [Resolved] + private ScoreManager scoreManager { get; set; } + + private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); private readonly Flow flow; private readonly Scroll scroll; private ScorePanel expandedPanel; @@ -115,32 +120,33 @@ namespace osu.Game.Screens.Ranking }; }); - flow.Add(panel.CreateTrackingContainer().With(d => - { - d.Anchor = Anchor.Centre; - d.Origin = Anchor.Centre; - })); + scoreManager.GetOrderedScoresAsync(new[] { score }) + .ContinueWith(_ => Schedule(() => + { + flow.Add(panel.CreateTrackingContainer().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + })); - if (IsLoaded) - { - if (SelectedScore.Value == score) - { - SelectedScore.TriggerChange(); - } - else - { - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) - { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; - } - } - } + if (SelectedScore.Value == score) + { + SelectedScore.TriggerChange(); + } + else + { + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } + } + })); return panel; } @@ -286,6 +292,12 @@ namespace osu.Game.Screens.Ranking return base.OnKeyDown(e); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + loadCancellationSource?.Cancel(); + } + private class Flow : FillFlowContainer { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 0276ae8b2a..54ec42127d 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -66,6 +68,9 @@ namespace osu.Game.Screens.Select.Leaderboards [Resolved] private ScoreManager scoreManager { get; set; } + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + [Resolved] private IBindable ruleset { get; set; } @@ -120,8 +125,13 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local; + private CancellationTokenSource loadCancellationSource; + protected override APIRequest FetchScores(Action> scoresCallback) { + loadCancellationSource?.Cancel(); + loadCancellationSource = new CancellationTokenSource(); + if (Beatmap == null) { PlaceholderState = PlaceholderState.NoneSelected; @@ -146,11 +156,8 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); } - Scores = scores.OrderByDescending(s => scoreManager.GetTotalScore(s)) - .ThenBy(s => s.OnlineScoreID) - .ToArray(); - - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + scoreManager.GetOrderedScoresAsync(scores.ToArray(), loadCancellationSource.Token) + .ContinueWith(ordered => scoresCallback?.Invoke(ordered.Result), TaskContinuationOptions.OnlyOnRanToCompletion); return null; } @@ -185,12 +192,15 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoresCallback?.Invoke( - r.Scores.Select(s => s.CreateScoreInfo(rulesets)) - .OrderByDescending(s => scoreManager.GetTotalScore(s)) - .ThenBy(s => s.OnlineScoreID)); + scoreManager.GetOrderedScoresAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + .ContinueWith(ordered => Schedule(() => + { + if (loadCancellationSource.IsCancellationRequested) + return; - TopScore = r.UserScore?.CreateScoreInfo(rulesets); + scoresCallback?.Invoke(ordered.Result); + TopScore = r.UserScore?.CreateScoreInfo(rulesets); + }), TaskContinuationOptions.OnlyOnRanToCompletion); }; return req; From 999386da2919a022132970344a2d089f192c9d2c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 31 Aug 2021 21:47:49 +0900 Subject: [PATCH 12/75] Cleanup --- osu.Game/Online/Leaderboards/Leaderboard.cs | 3 +-- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 1d9d2e6b14..4f8b27602b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Threading; -using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; @@ -247,7 +246,7 @@ namespace osu.Game.Online.Leaderboards private readonly IBindable apiState = new Bindable(); [BackgroundDependencyLoader] - private void load(OsuConfigManager configManager) + private void load() { if (api != null) apiState.BindTo(api.State); diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 9a0bc35bb3..934b905a1a 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -198,8 +198,8 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), + Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.Numeric.With(size: 23), - Current = scoreManager.GetBindableTotalScoreString(score) }, RankContainer = new Container { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 9497ed9d35..a154016824 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -151,8 +151,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new OsuSpriteText { Margin = new MarginPadding { Right = horizontal_inset }, - Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium), Current = scoreManager.GetBindableTotalScoreString(score), + Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, new OsuSpriteText { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 8e9a9eb684..a5a373467e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -182,7 +182,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuConfigManager config) + private void load(OverlayColourProvider colourProvider) { background.Colour = colourProvider.Background5; From 88fc53200ed880b5836d1e6c1497e33dd8bb6316 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Sep 2021 15:41:52 +0900 Subject: [PATCH 13/75] Refactor --- .../BeatmapSet/Scores/ScoresContainer.cs | 3 +- osu.Game/Scoring/ScoreManager.cs | 36 ++++++++---- osu.Game/Screens/Ranking/ScorePanelList.cs | 56 ++++++++++--------- .../Select/Leaderboards/BeatmapLeaderboard.cs | 4 +- 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index a5a373467e..fb1769fbe1 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -14,7 +14,6 @@ using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Framework.Bindables; -using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -67,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (value?.Scores.Any() != true) return; - scoreManager.GetOrderedScoresAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) .ContinueWith(ordered => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 71edc65fcc..6fccf80b6c 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -34,7 +34,6 @@ namespace osu.Game.Scoring protected override string ImportFromStablePath => Path.Combine("Data", "r"); - private readonly Bindable scoringMode = new Bindable(); private readonly RulesetStore rulesets; private readonly Func beatmaps; @@ -52,8 +51,6 @@ namespace osu.Game.Scoring this.beatmaps = beatmaps; this.difficulties = difficulties; this.configManager = configManager; - - configManager?.BindWith(OsuSetting.ScoreDisplayMode, scoringMode); } protected override ScoreInfo CreateModel(ArchiveReader archive) @@ -106,14 +103,20 @@ namespace osu.Game.Scoring => base.CheckLocalAvailability(model, items) || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); - public async Task GetOrderedScoresAsync(ScoreInfo[] scores, CancellationToken cancellationToken = default) + /// + /// Orders an array of s by total score. + /// + /// The array of s to reorder. + /// A to cancel the process. + /// The given ordered by decreasing total score. + public async Task OrderByTotalScoreAsync(ScoreInfo[] scores, CancellationToken cancellationToken = default) { var difficultyCache = difficulties?.Invoke(); if (difficultyCache == null) return orderByTotalScore(scores); - // Compute difficulties asynchronously first to prevent blocks on the main thread. + // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. foreach (var s in scores) { await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); @@ -122,7 +125,7 @@ namespace osu.Game.Scoring return orderByTotalScore(scores); - ScoreInfo[] orderByTotalScore(IEnumerable incoming) => incoming.OrderByDescending(GetTotalScore).ThenBy(s => s.OnlineScoreID).ToArray(); + ScoreInfo[] orderByTotalScore(IEnumerable incoming) => incoming.OrderByDescending(s => GetTotalScore(s)).ThenBy(s => s.OnlineScoreID).ToArray(); } /// @@ -150,9 +153,22 @@ namespace osu.Game.Scoring /// The bindable containing the formatted total score string. public Bindable GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); - public long GetTotalScore(ScoreInfo score) => GetTotalScoreAsync(score).Result; + /// + /// Retrieves the total score of a in the given . + /// + /// The to calculate the total score of. + /// The to return the total score as. + /// The total score. + public long GetTotalScore(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised) => GetTotalScoreAsync(score).Result; - public async Task GetTotalScoreAsync(ScoreInfo score, CancellationToken cancellationToken = default) + /// + /// Retrieves the total score of a in the given . + /// + /// The to calculate the total score of. + /// The to return the total score as. + /// A to cancel the process. + /// The total score. + public async Task GetTotalScoreAsync(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { if (score.Beatmap == null) return score.TotalScore; @@ -204,7 +220,7 @@ namespace osu.Game.Scoring var scoreProcessor = ruleset.CreateScoreProcessor(); scoreProcessor.Mods.Value = score.Mods; - return (long)Math.Round(scoreProcessor.GetScore(scoringMode.Value, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + return (long)Math.Round(scoreProcessor.GetScore(mode, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); } /// @@ -221,7 +237,7 @@ namespace osu.Game.Scoring /// The . public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager) { - ScoringMode.BindValueChanged(_ => Value = scoreManager.GetTotalScore(score), true); + ScoringMode.BindValueChanged(mode => Value = scoreManager.GetTotalScore(score, mode.NewValue), true); } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index e319e824a1..b0c06ebe6a 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Scoring; using osuTK; @@ -65,6 +66,9 @@ namespace osu.Game.Screens.Ranking [Resolved] private ScoreManager scoreManager { get; set; } + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); private readonly Flow flow; private readonly Scroll scroll; @@ -120,33 +124,33 @@ namespace osu.Game.Screens.Ranking }; }); - scoreManager.GetOrderedScoresAsync(new[] { score }) - .ContinueWith(_ => Schedule(() => - { - flow.Add(panel.CreateTrackingContainer().With(d => - { - d.Anchor = Anchor.Centre; - d.Origin = Anchor.Centre; - })); + difficultyCache.GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods) + .ContinueWith(_ => Schedule(() => + { + flow.Add(panel.CreateTrackingContainer().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + })); - if (SelectedScore.Value == score) - { - SelectedScore.TriggerChange(); - } - else - { - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) - { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; - } - } - })); + if (SelectedScore.Value == score) + { + SelectedScore.TriggerChange(); + } + else + { + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } + } + })); return panel; } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 54ec42127d..7820264505 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym))); } - scoreManager.GetOrderedScoresAsync(scores.ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(scores.ToArray(), loadCancellationSource.Token) .ContinueWith(ordered => scoresCallback?.Invoke(ordered.Result), TaskContinuationOptions.OnlyOnRanToCompletion); return null; @@ -192,7 +192,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.GetOrderedScoresAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) .ContinueWith(ordered => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) From ab538dc3dd4474961c2235c46d4223892e29cf60 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Sep 2021 20:30:26 +0900 Subject: [PATCH 14/75] Fix param not passed through --- osu.Game/Scoring/ScoreManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6fccf80b6c..9b94e34f75 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -159,7 +159,7 @@ namespace osu.Game.Scoring /// The to calculate the total score of. /// The to return the total score as. /// The total score. - public long GetTotalScore(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised) => GetTotalScoreAsync(score).Result; + public long GetTotalScore(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised) => GetTotalScoreAsync(score, mode).Result; /// /// Retrieves the total score of a in the given . From f7c1177cc9ec585583a549e81e834aa581819e64 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Sep 2021 20:35:06 +0900 Subject: [PATCH 15/75] Fix ScorePanelList nullref when scores are added too soon --- .../Visual/Ranking/TestSceneScorePanelList.cs | 16 ++++ osu.Game/Screens/Ranking/ScorePanelList.cs | 95 ++++++++++--------- 2 files changed, 65 insertions(+), 46 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index e65dcb19b1..f330e99d55 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -182,6 +182,22 @@ namespace osu.Game.Tests.Visual.Ranking assertExpandedPanelCentred(); } + [Test] + public void TestAddScoreImmediately() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => + { + var newList = new ScorePanelList { SelectedScore = { Value = score } }; + newList.AddScore(score); + return newList; + }); + + assertScoreState(score, true); + assertExpandedPanelCentred(); + } + private void createListStep(Func creationFunc) { AddStep("create list", () => Child = list = creationFunc().With(d => diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index b0c06ebe6a..711e0d60ec 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -100,6 +101,9 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); + foreach (var d in flow) + displayScore(d); + SelectedScore.BindValueChanged(selectedScoreChanged, true); } @@ -124,37 +128,56 @@ namespace osu.Game.Screens.Ranking }; }); - difficultyCache.GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods) - .ContinueWith(_ => Schedule(() => - { - flow.Add(panel.CreateTrackingContainer().With(d => - { - d.Anchor = Anchor.Centre; - d.Origin = Anchor.Centre; - })); + var trackingContainer = panel.CreateTrackingContainer().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + d.Hide(); + }); - if (SelectedScore.Value == score) - { - SelectedScore.TriggerChange(); - } - else - { - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) - { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; - } - } - })); + flow.Add(trackingContainer); + + if (IsLoaded) + displayScore(trackingContainer); return panel; } + private void displayScore(ScorePanelTrackingContainer trackingContainer) + { + if (!IsLoaded) + return; + + var score = trackingContainer.Panel.Score; + + // Calculating score can take a while in extreme scenarios, so only display scores after the process completes. + scoreManager.GetTotalScoreAsync(score) + .ContinueWith(totalScore => Schedule(() => + { + flow.SetLayoutPosition(trackingContainer, totalScore.Result); + + trackingContainer.Show(); + + if (SelectedScore.Value == score) + { + SelectedScore.TriggerChange(); + } + else + { + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } + } + }), TaskContinuationOptions.OnlyOnRanToCompletion); + } + /// /// Brings a to the centre of the screen and expands it. /// @@ -306,28 +329,8 @@ namespace osu.Game.Screens.Ranking { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); - [Resolved] - private ScoreManager scoreManager { get; set; } - - protected override void LoadComplete() - { - base.LoadComplete(); - - foreach (var c in Children) - SetLayoutPosition(c, scoreManager.GetTotalScore(c.Panel.Score)); - } - public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); - public override void Add(ScorePanelTrackingContainer drawable) - { - Debug.Assert(drawable != null); - - base.Add(drawable); - - SetLayoutPosition(drawable, scoreManager?.GetTotalScore(drawable.Panel.Score) ?? 0); - } - private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) .ThenBy(s => s.Panel.Score.OnlineScoreID); From df7480e68cc4d8d3f3fcec7a70e2b4c4db0b4854 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Sep 2021 20:56:23 +0900 Subject: [PATCH 16/75] Fix bindable implementation being synchronous --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- .../TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 49 +++++++++++++++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 184a2e59da..29815ce9ff 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler)); return dependencies; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 3f9e0048dd..98482601ee 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler)); beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0]; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9b94e34f75..2cda8959f4 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -13,6 +13,7 @@ using Microsoft.EntityFrameworkCore; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; @@ -36,6 +37,7 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; + private readonly Scheduler scheduler; [CanBeNull] private readonly Func difficulties; @@ -43,12 +45,13 @@ namespace osu.Game.Scoring [CanBeNull] private readonly OsuConfigManager configManager; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null, - Func difficulties = null, OsuConfigManager configManager = null) + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, Scheduler scheduler, + IIpcHost importHost = null, Func difficulties = null, OsuConfigManager configManager = null) : base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost) { this.rulesets = rulesets; this.beatmaps = beatmaps; + this.scheduler = scheduler; this.difficulties = difficulties; this.configManager = configManager; } @@ -125,7 +128,13 @@ namespace osu.Game.Scoring return orderByTotalScore(scores); - ScoreInfo[] orderByTotalScore(IEnumerable incoming) => incoming.OrderByDescending(s => GetTotalScore(s)).ThenBy(s => s.OnlineScoreID).ToArray(); + ScoreInfo[] orderByTotalScore(IEnumerable incoming) + { + // We're calling .Result, but this should not be a blocking call due to the above GetDifficultyAsync() calls. + return incoming.OrderByDescending(s => GetTotalScoreAsync(s, cancellationToken: cancellationToken).Result) + .ThenBy(s => s.OnlineScoreID) + .ToArray(); + } } /// @@ -136,7 +145,7 @@ namespace osu.Game.Scoring /// /// The to retrieve the bindable for. /// The bindable containing the total score. - public Bindable GetBindableTotalScore(ScoreInfo score) + public Bindable GetBindableTotalScore([NotNull] ScoreInfo score) { var bindable = new TotalScoreBindable(score, this); configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode); @@ -151,15 +160,21 @@ namespace osu.Game.Scoring /// /// The to retrieve the bindable for. /// The bindable containing the formatted total score string. - public Bindable GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); + public Bindable GetBindableTotalScoreString([NotNull] ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); /// /// Retrieves the total score of a in the given . + /// The score is returned in a callback that is run on the update thread. /// /// The to calculate the total score of. + /// The callback to be invoked with the total score. /// The to return the total score as. - /// The total score. - public long GetTotalScore(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised) => GetTotalScoreAsync(score, mode).Result; + /// A to cancel the process. + public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) + { + GetTotalScoreAsync(score, mode, cancellationToken) + .ContinueWith(s => scheduler.Add(() => callback(s.Result)), TaskContinuationOptions.OnlyOnRanToCompletion); + } /// /// Retrieves the total score of a in the given . @@ -168,7 +183,7 @@ namespace osu.Game.Scoring /// The to return the total score as. /// A to cancel the process. /// The total score. - public async Task GetTotalScoreAsync(ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) + public async Task GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { if (score.Beatmap == null) return score.TotalScore; @@ -230,6 +245,11 @@ namespace osu.Game.Scoring { public readonly Bindable ScoringMode = new Bindable(); + private readonly ScoreInfo score; + private readonly ScoreManager scoreManager; + + private CancellationTokenSource difficultyCalculationCancellationSource; + /// /// Creates a new . /// @@ -237,7 +257,18 @@ namespace osu.Game.Scoring /// The . public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager) { - ScoringMode.BindValueChanged(mode => Value = scoreManager.GetTotalScore(score, mode.NewValue), true); + this.score = score; + this.scoreManager = scoreManager; + + ScoringMode.BindValueChanged(onScoringModeChanged, true); + } + + private void onScoringModeChanged(ValueChangedEvent mode) + { + difficultyCalculationCancellationSource?.Cancel(); + difficultyCalculationCancellationSource = new CancellationTokenSource(); + + scoreManager.GetTotalScore(score, s => Value = s, mode.NewValue, difficultyCalculationCancellationSource.Token); } } From f14d66aafce778d86e2cc203afd0828d4c60293c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 2 Sep 2021 10:35:00 +0900 Subject: [PATCH 17/75] Commit missed line --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 69f6bc1b7b..16e3cdbfda 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -262,7 +262,7 @@ namespace osu.Game dependencies.Cache(fileStore = new FileStore(contextFactory, Storage)); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, true)); // this should likely be moved to ArchiveModelManager when another case appears where it is necessary From 000b85a860cb4e6f2f8cf12884fdf85adf1799aa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 17:56:56 +0900 Subject: [PATCH 18/75] Add GH Actions workflow for diffcalc checks --- .github/workflows/test-diffcalc.yml | 150 ++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 .github/workflows/test-diffcalc.yml diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml new file mode 100644 index 0000000000..b703c97735 --- /dev/null +++ b/.github/workflows/test-diffcalc.yml @@ -0,0 +1,150 @@ +name: Diffcalc Consistency Checks +on: + issue_comment: + types: [ created ] + +env: + DB_USER: root + DB_HOST: 127.0.0.1 + CONCURRENCY: 4 + ALLOW_DOWNLOAD: 1 + SAVE_DOWNLOADED: 1 + +jobs: + diffcalc: + name: Diffcalc + runs-on: ubuntu-latest + + if: | + contains(github.event.comment.html_url, '/pull/') && + contains(github.event.comment.body, '!pp check') && + ${{ github.event.comment.author_association == 'MEMBER' }} + + + strategy: + fail-fast: false + matrix: + ruleset: + - { name: osu, id: 0 } + - { name: taiko, id: 1 } + - { name: catch, id: 2 } + - { name: mania, id: 3 } + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + steps: + - name: Verify MySQL connection from host + run: | + sudo apt-get install -y mysql-client + mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "SHOW DATABASES" + + - name: Create directory structure + run: | + mkdir -p $GITHUB_WORKSPACE/master/ + mkdir -p $GITHUB_WORKSPACE/pr/ + + # Checkout osu + - name: Checkout osu (master) + uses: actions/checkout@v2 + with: + repository: ppy/osu + path: 'master/osu' + - name: Checkout osu (pr) + uses: actions/checkout@v2 + with: + path: 'pr/osu' + + # Checkout osu-difficulty-calculator + - name: Checkout osu-difficulty-calculator (master) + uses: actions/checkout@v2 + with: + repository: ppy/osu-difficulty-calculator + path: 'master/osu-difficulty-calculator' + - name: Checkout osu-difficulty-calculator (pr) + uses: actions/checkout@v2 + with: + repository: ppy/osu-difficulty-calculator + path: 'pr/osu-difficulty-calculator' + + - name: Install .NET 5.0.x + uses: actions/setup-dotnet@v1 + with: + dotnet-version: "5.0.x" + + # Sanity checks to make sure diffcalc is not run when incompatible. + - name: Build diffcalc (master) + run: | + cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator + ./UseLocalOsu.sh + dotnet build + - name: Build diffcalc (pr) + run: | + cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator + ./UseLocalOsu.sh + dotnet build + + # Initial data imports + - name: Download + import data + run: | + PERFORMANCE_DATA_NAME=$(date +'%Y_%m_01_performance_${{ matrix.ruleset.name }}_random') + BEATMAPS_DATA_NAME=$(date +'%Y_%m_01_osu_files') + + # Set env variable for further steps. + echo "BEATMAPS_PATH=$GITHUB_WORKSPACE/$BEATMAPS_DATA_NAME" >> $GITHUB_ENV + + cd $GITHUB_WORKSPACE + + wget https://data.ppy.sh/$PERFORMANCE_DATA_NAME.tar.bz2 + wget https://data.ppy.sh/$BEATMAPS_DATA_NAME.tar.bz2 + tar -xf $PERFORMANCE_DATA_NAME.tar.bz2 + tar -xf $BEATMAPS_DATA_NAME.tar.bz2 + + cd $GITHUB_WORKSPACE/$PERFORMANCE_DATA_NAME + + mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "CREATE DATABASE osu_master" + mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "CREATE DATABASE osu_pr" + + cat *.sql | mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} --database=osu_master + cat *.sql | mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} --database=osu_pr + + # Run diffcalc + - name: Run diffcalc (master) + env: + DB_NAME: osu_master + run: | + cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator/osu.Server.DifficultyCalculator + dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }} + - name: Run diffcalc (pr) + env: + DB_NAME: osu_pr + run: | + cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator/osu.Server.DifficultyCalculator + dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }} + + # Print diffs + - name: Print diffs + run: | + mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e " + SELECT + m.beatmap_id, + m.mods, + m.diff_unified as `sr_master`, + p.diff_unified as `sr_pr`, + (p.diff_unified - m.diff_unified) as `diff` + FROM osu_master.osu_beatmap_difficulty m + JOIN osu_pr.osu_beatmap_difficulty p + ON m.beatmap_id = p.beatmap_id + AND m.mode = p.mode + AND m.mods = p.mods + WHERE abs(m.diff_unified - p.diff_unified) > 0.1 + ORDER BY abs(m.diff_unified - p.diff_unified) + DESC;" + + # Todo: Run ppcalc \ No newline at end of file From 176c414c112e1321a65fde03b78ecf72a479285a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 18:10:16 +0900 Subject: [PATCH 19/75] More accurate data retrieval --- .github/workflows/test-diffcalc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index b703c97735..3d80f08ebf 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -93,8 +93,8 @@ jobs: # Initial data imports - name: Download + import data run: | - PERFORMANCE_DATA_NAME=$(date +'%Y_%m_01_performance_${{ matrix.ruleset.name }}_random') - BEATMAPS_DATA_NAME=$(date +'%Y_%m_01_osu_files') + PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_random | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') + BEATMAPS_DATA_NAME=$(curl https://data.ppy.sh/ | grep osu_files | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') # Set env variable for further steps. echo "BEATMAPS_PATH=$GITHUB_WORKSPACE/$BEATMAPS_DATA_NAME" >> $GITHUB_ENV From b8ada31d7de0b4fa47abd50a84d83a67e409eec5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 18:47:15 +0900 Subject: [PATCH 20/75] Match against individual rulesets --- .github/workflows/test-diffcalc.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 3d80f08ebf..48e1e238c1 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -1,3 +1,8 @@ +# Listens to new PR comments containing "!pp check", and runs diffcalc across the PR and master to produce a table of differences. +# Usage: +# !pp check 0 : Runs only the osu! ruleset +# !pp check 0 1 : Runs the osu! and taiko rulesets. + name: Diffcalc Consistency Checks on: issue_comment: @@ -18,8 +23,8 @@ jobs: if: | contains(github.event.comment.html_url, '/pull/') && contains(github.event.comment.body, '!pp check') && - ${{ github.event.comment.author_association == 'MEMBER' }} - + ${{ github.event.comment.author_association == 'MEMBER' }} && + contains(github.event.comment.body, ${{ matrix.ruleset.id }}) strategy: fail-fast: false From 789c108e8d426fba356d4f424427f558c2b19ec8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 18:56:56 +0900 Subject: [PATCH 21/75] Fix if condition --- .github/workflows/test-diffcalc.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 48e1e238c1..64ffa84c5e 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -23,8 +23,7 @@ jobs: if: | contains(github.event.comment.html_url, '/pull/') && contains(github.event.comment.body, '!pp check') && - ${{ github.event.comment.author_association == 'MEMBER' }} && - contains(github.event.comment.body, ${{ matrix.ruleset.id }}) + ${{ github.event.comment.author_association == 'MEMBER' }} strategy: fail-fast: false @@ -34,6 +33,10 @@ jobs: - { name: taiko, id: 1 } - { name: catch, id: 2 } - { name: mania, id: 3 } + isValidRuleset: + - contains(github.event.comment.body, ${{ matrix.ruleset.id }}) + exclude: + - isValidRuleset: false services: mysql: From 33dcb4915b28e36e354151eb8e7ee929695bddd7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 19:12:41 +0900 Subject: [PATCH 22/75] Revert "Fix if condition" This reverts commit 789c108e8d426fba356d4f424427f558c2b19ec8. --- .github/workflows/test-diffcalc.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 64ffa84c5e..48e1e238c1 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -23,7 +23,8 @@ jobs: if: | contains(github.event.comment.html_url, '/pull/') && contains(github.event.comment.body, '!pp check') && - ${{ github.event.comment.author_association == 'MEMBER' }} + ${{ github.event.comment.author_association == 'MEMBER' }} && + contains(github.event.comment.body, ${{ matrix.ruleset.id }}) strategy: fail-fast: false @@ -33,10 +34,6 @@ jobs: - { name: taiko, id: 1 } - { name: catch, id: 2 } - { name: mania, id: 3 } - isValidRuleset: - - contains(github.event.comment.body, ${{ matrix.ruleset.id }}) - exclude: - - isValidRuleset: false services: mysql: From 54389561aaefd5b1f69f1aa5b360260ba1328676 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 19:12:55 +0900 Subject: [PATCH 23/75] Revert "Match against individual rulesets" This reverts commit b8ada31d7de0b4fa47abd50a84d83a67e409eec5. --- .github/workflows/test-diffcalc.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 48e1e238c1..3d80f08ebf 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -1,8 +1,3 @@ -# Listens to new PR comments containing "!pp check", and runs diffcalc across the PR and master to produce a table of differences. -# Usage: -# !pp check 0 : Runs only the osu! ruleset -# !pp check 0 1 : Runs the osu! and taiko rulesets. - name: Diffcalc Consistency Checks on: issue_comment: @@ -23,8 +18,8 @@ jobs: if: | contains(github.event.comment.html_url, '/pull/') && contains(github.event.comment.body, '!pp check') && - ${{ github.event.comment.author_association == 'MEMBER' }} && - contains(github.event.comment.body, ${{ matrix.ruleset.id }}) + ${{ github.event.comment.author_association == 'MEMBER' }} + strategy: fail-fast: false From 893a4d43657ab4d335d951cee8e22708d00e09b4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 19:23:55 +0900 Subject: [PATCH 24/75] Fix incorrect quoting --- .github/workflows/test-diffcalc.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 3d80f08ebf..1e5c033a67 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -135,9 +135,9 @@ jobs: SELECT m.beatmap_id, m.mods, - m.diff_unified as `sr_master`, - p.diff_unified as `sr_pr`, - (p.diff_unified - m.diff_unified) as `diff` + m.diff_unified as 'sr_master', + p.diff_unified as 'sr_pr', + (p.diff_unified - m.diff_unified) as 'diff' FROM osu_master.osu_beatmap_difficulty m JOIN osu_pr.osu_beatmap_difficulty p ON m.beatmap_id = p.beatmap_id From c6d71e1ae8bb7d3862abd063db7e285f311e4d2d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 20:10:17 +0900 Subject: [PATCH 25/75] Add back ruleset check --- .github/workflows/test-diffcalc.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 1e5c033a67..ab7107f594 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -20,7 +20,6 @@ jobs: contains(github.event.comment.body, '!pp check') && ${{ github.event.comment.author_association == 'MEMBER' }} - strategy: fail-fast: false matrix: @@ -40,6 +39,12 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: + - name: Verify ruleset + if: contains(github.event.comment.body, matrix.ruleset.id) == false + run: | + echo "${{ github.event.comment.body }} doesn't contain ${{ matrix.ruleset.id }}" + exit 1 + - name: Verify MySQL connection from host run: | sudo apt-get install -y mysql-client From 1f7f8bb18992d1a8a558eb432acdc1a7e55e30ec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 20:13:33 +0900 Subject: [PATCH 26/75] Add description to workflow --- .github/workflows/test-diffcalc.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index ab7107f594..c6b2f254c8 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -1,3 +1,9 @@ +# Listens for new PR comments containing !pp check [id], and runs a diffcalc comparison against master. +# Usage: +# !pp check 0 | Runs only the osu! ruleset. +# !pp check 0 2 | Runs only the osu! and catch rulesets. +# + name: Diffcalc Consistency Checks on: issue_comment: From 88158b79f8e5ad2154fa1efd0f89e10532421600 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Sep 2021 20:37:38 +0900 Subject: [PATCH 27/75] Change to using top scores --- .github/workflows/test-diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index c6b2f254c8..3bec11928f 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -104,7 +104,7 @@ jobs: # Initial data imports - name: Download + import data run: | - PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_random | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') + PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_top | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') BEATMAPS_DATA_NAME=$(curl https://data.ppy.sh/ | grep osu_files | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') # Set env variable for further steps. From 16beb2c90c1884d8829f52cb92142c06498d3b00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 15:35:46 +0900 Subject: [PATCH 28/75] Expose more pieces of `TabletSettings` --- .../Settings/Sections/Input/TabletAreaSelection.cs | 8 +++++--- .../Overlays/Settings/Sections/Input/TabletSettings.cs | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 412889d210..e18cf7e1c2 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -17,6 +17,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { public class TabletAreaSelection : CompositeDrawable { + public bool IsWithinBounds { get; private set; } + private readonly ITabletHandler handler; private Container tabletContainer; @@ -171,10 +173,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input var usableSsdq = usableAreaContainer.ScreenSpaceDrawQuad; - bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft + new Vector2(1)) && - tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight - new Vector2(1)); + IsWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft + new Vector2(1)) && + tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight - new Vector2(1)); - usableFill.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); + usableFill.FadeColour(IsWithinBounds ? colour.Blue : colour.RedLight, 100); } protected override void Update() diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index b8b86d9069..8c60e81fb5 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -20,6 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { public class TabletSettings : SettingsSubsection { + public TabletAreaSelection AreaSelection { get; private set; } + private readonly ITabletHandler tabletHandler; private readonly Bindable enabled = new BindableBool(true); @@ -121,7 +123,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input Direction = FillDirection.Vertical, Children = new Drawable[] { - new TabletAreaSelection(tabletHandler) + AreaSelection = new TabletAreaSelection(tabletHandler) { RelativeSizeAxes = Axes.X, Height = 300, From 8d44f059ec952080516bb6f811676404bde64ebb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 15:35:54 +0900 Subject: [PATCH 29/75] Add test coverage of failing validity checks --- .../Settings/TestSceneTabletSettings.cs | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index da474a64ba..0202393973 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -2,11 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; -using osu.Framework.Platform; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Input; @@ -17,22 +16,34 @@ namespace osu.Game.Tests.Visual.Settings [TestFixture] public class TestSceneTabletSettings : OsuTestScene { - [BackgroundDependencyLoader] - private void load(GameHost host) - { - var tabletHandler = new TestTabletHandler(); + private TestTabletHandler tabletHandler; + private TabletSettings settings; - AddRange(new Drawable[] + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create settings", () => { - new TabletSettings(tabletHandler) + tabletHandler = new TestTabletHandler(); + + Children = new Drawable[] { - RelativeSizeAxes = Axes.None, - Width = SettingsPanel.PANEL_WIDTH, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - } + settings = new TabletSettings(tabletHandler) + { + RelativeSizeAxes = Axes.None, + Width = SettingsPanel.PANEL_WIDTH, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + }; }); + AddStep("set square size", () => tabletHandler.SetTabletSize(new Vector2(100, 100))); + } + + [Test] + public void TestVariousTabletSizes() + { AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100))); AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Vector2(300, 300))); AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Vector2(100, 300))); @@ -40,6 +51,24 @@ namespace osu.Game.Tests.Visual.Settings AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(Vector2.Zero)); } + [Test] + public void TestValidAfterRotation() + { + AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + AddStep("rotate 90", () => tabletHandler.Rotation.Value = 90); + AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + AddStep("rotate 180", () => tabletHandler.Rotation.Value = 180); + AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + AddStep("rotate 360", () => tabletHandler.Rotation.Value = 360); + AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); + AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + } + public class TestTabletHandler : ITabletHandler { public Bindable AreaOffset { get; } = new Bindable(); From 66daa553de2eac46f35d511a34ff959d1b3ab9c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 19:34:55 +0900 Subject: [PATCH 30/75] Fix bounds check running too early causing tablet area to show incorrect validity --- .../Overlays/Settings/Sections/Input/TabletAreaSelection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index e18cf7e1c2..4f27a2da70 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -131,9 +131,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input rotation.BindTo(handler.Rotation); rotation.BindValueChanged(val => { - tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint); - usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint) - .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint); + tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint) + .OnComplete(_ => checkBounds()); // required as we are using SSDQ. }, true); tablet.BindTo(handler.Tablet); From 7b26e480e6492348bb0c91b65a20c9f3d9cbfcf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 22:55:14 +0900 Subject: [PATCH 31/75] Add extended tests --- .../Settings/TestSceneTabletSettings.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 0202393973..a854bec1a9 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Testing; using osu.Framework.Utils; @@ -57,15 +59,27 @@ namespace osu.Game.Tests.Visual.Settings AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); AddStep("rotate 90", () => tabletHandler.Rotation.Value = 90); - AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + ensureValid(); AddStep("rotate 180", () => tabletHandler.Rotation.Value = 180); - AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + ensureValid(); + + AddStep("rotate 270", () => tabletHandler.Rotation.Value = 270); + + ensureValid(); AddStep("rotate 360", () => tabletHandler.Rotation.Value = 360); - AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); + + ensureValid(); AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); + ensureValid(); + } + + private void ensureValid() + { + AddUntilStep("wait for transforms", () => settings.AreaSelection.ChildrenOfType().All(c => !c.Transforms.Any())); AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); } From 94b34a5474cf625b274f3b6f84477417b62ee179 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 23:19:05 +0900 Subject: [PATCH 32/75] Add test coverage of invalid cases too --- .../Settings/TestSceneTabletSettings.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index a854bec1a9..d5bcbc5fd9 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Settings } [Test] - public void TestValidAfterRotation() + public void TestRotationValidity() { AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); @@ -75,6 +75,22 @@ namespace osu.Game.Tests.Visual.Settings AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); ensureValid(); + + AddStep("rotate 0", () => tabletHandler.Rotation.Value = 45); + ensureInvalid(); + + AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); + ensureValid(); + } + + [Test] + public void TestOffsetValidity() + { + ensureValid(); + AddStep("move right", () => tabletHandler.AreaOffset.Value = Vector2.Zero); + ensureInvalid(); + AddStep("move back", () => tabletHandler.AreaOffset.Value = tabletHandler.AreaSize.Value / 2); + ensureValid(); } private void ensureValid() @@ -83,6 +99,12 @@ namespace osu.Game.Tests.Visual.Settings AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); } + private void ensureInvalid() + { + AddUntilStep("wait for transforms", () => settings.AreaSelection.ChildrenOfType().All(c => !c.Transforms.Any())); + AddAssert("area invalid", () => !settings.AreaSelection.IsWithinBounds); + } + public class TestTabletHandler : ITabletHandler { public Bindable AreaOffset { get; } = new Bindable(); From 4fb3a1d64162866cb973efeedbe456af2eba8603 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Sep 2021 23:08:35 +0900 Subject: [PATCH 33/75] Update check to inflate in the correct direct Also handles previously unhandled edge cases by comparing all four corners, instead of only two. --- .../Sections/Input/TabletAreaSelection.cs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 4f27a2da70..d12052b24d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -4,10 +4,13 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.MatrixExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Handlers.Tablet; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -131,9 +134,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input rotation.BindTo(handler.Rotation); rotation.BindValueChanged(val => { - usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint); - tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint) - .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint) + .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint); }, true); tablet.BindTo(handler.Tablet); @@ -171,10 +174,22 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (tablet.Value == null) return; - var usableSsdq = usableAreaContainer.ScreenSpaceDrawQuad; + // All of this manual logic is just to get around floating point issues when doing a contains check on the screen quads. + // This is best effort, as it's only used for display purposes. If we need for anything more, manual math on the raw values should be preferred. + var containerQuad = tabletContainer.ScreenSpaceDrawQuad.AABBFloat.Inflate(1); + var usableAreaQuad = Quad.FromRectangle(usableAreaContainer.ScreenSpaceDrawQuad.AABBFloat); - IsWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft + new Vector2(1)) && - tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight - new Vector2(1)); + var matrix = Matrix3.Identity; + MatrixExtensions.TranslateFromLeft(ref matrix, usableAreaQuad.Centre); + MatrixExtensions.RotateFromLeft(ref matrix, MathUtils.DegreesToRadians(rotation.Value)); + MatrixExtensions.TranslateFromLeft(ref matrix, -usableAreaQuad.Centre); + usableAreaQuad *= matrix; + + IsWithinBounds = + containerQuad.Contains(usableAreaQuad.TopLeft) && + containerQuad.Contains(usableAreaQuad.TopRight) && + containerQuad.Contains(usableAreaQuad.BottomLeft) && + containerQuad.Contains(usableAreaQuad.BottomRight); usableFill.FadeColour(IsWithinBounds ? colour.Blue : colour.RedLight, 100); } From f76f12d361a97238797b7115003bbfc9f79f5272 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Sep 2021 11:14:28 +0900 Subject: [PATCH 34/75] Fix incorrect test step name Co-authored-by: PercyDan <50285552+PercyDan54@users.noreply.github.com> --- osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index d5bcbc5fd9..49d6b7033e 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); ensureValid(); - AddStep("rotate 0", () => tabletHandler.Rotation.Value = 45); + AddStep("rotate 45", () => tabletHandler.Rotation.Value = 45); ensureInvalid(); AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0); From e8fb5d2e663be713cfba16e967053952305f6e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 16:07:24 +0200 Subject: [PATCH 35/75] Add non-functional difficulty switcher to menu --- osu.Game/Screens/Edit/Editor.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 57c78f3c65..a9fcf29c51 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -703,6 +703,13 @@ namespace osu.Game.Screens.Edit if (RuntimeInfo.IsDesktop) fileMenuItems.Add(new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap)); + fileMenuItems.Add(new EditorMenuItemSpacer()); + + var beatmapSet = beatmapManager.QueryBeatmapSet(bs => bs.ID == Beatmap.Value.BeatmapSetInfo.ID) ?? playableBeatmap.BeatmapInfo.BeatmapSet; + var difficultyItems = beatmapSet.Beatmaps.Select(b => new EditorMenuItem(b.Version ?? "(unnamed)")).ToList(); + + fileMenuItems.Add(new EditorMenuItem("Change difficulty") { Items = difficultyItems }); + fileMenuItems.Add(new EditorMenuItemSpacer()); fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); return fileMenuItems; From 90f0b6874f4fdda9e3b1dee6d7d2dec883e4cfb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 16:10:49 +0200 Subject: [PATCH 36/75] Highlight current difficulty in switcher --- osu.Game/Screens/Edit/Editor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a9fcf29c51..cb78a19636 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -706,7 +706,10 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItemSpacer()); var beatmapSet = beatmapManager.QueryBeatmapSet(bs => bs.ID == Beatmap.Value.BeatmapSetInfo.ID) ?? playableBeatmap.BeatmapInfo.BeatmapSet; - var difficultyItems = beatmapSet.Beatmaps.Select(b => new EditorMenuItem(b.Version ?? "(unnamed)")).ToList(); + var difficultyItems = beatmapSet.Beatmaps.Select(b => new ToggleMenuItem(b.Version ?? "(unnamed)") + { + State = { Value = playableBeatmap.BeatmapInfo.Equals(b) } + }).ToList(); fileMenuItems.Add(new EditorMenuItem("Change difficulty") { Items = difficultyItems }); From 7befd030dfe3f8fb58c1886696ce07d737743b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 16:28:32 +0200 Subject: [PATCH 37/75] Minimal working example of switching difficulties --- osu.Game/Screens/Edit/Editor.cs | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index cb78a19636..c7ac232f58 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -35,7 +35,9 @@ using osu.Game.Screens.Edit.Design; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Verify; +using osu.Game.Screens.Menu; using osu.Game.Screens.Play; +using osu.Game.Screens.Select; using osu.Game.Users; using osuTK.Graphics; using osuTK.Input; @@ -101,6 +103,9 @@ namespace osu.Game.Screens.Edit [Resolved] private MusicController music { get; set; } + [Resolved(CanBeNull = true)] + private OsuGame game { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours, OsuConfigManager config) { @@ -706,10 +711,7 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItemSpacer()); var beatmapSet = beatmapManager.QueryBeatmapSet(bs => bs.ID == Beatmap.Value.BeatmapSetInfo.ID) ?? playableBeatmap.BeatmapInfo.BeatmapSet; - var difficultyItems = beatmapSet.Beatmaps.Select(b => new ToggleMenuItem(b.Version ?? "(unnamed)") - { - State = { Value = playableBeatmap.BeatmapInfo.Equals(b) } - }).ToList(); + var difficultyItems = beatmapSet.Beatmaps.Select(createDifficultyMenuItem).ToList(); fileMenuItems.Add(new EditorMenuItem("Change difficulty") { Items = difficultyItems }); @@ -718,6 +720,28 @@ namespace osu.Game.Screens.Edit return fileMenuItems; } + private ToggleMenuItem createDifficultyMenuItem(BeatmapInfo b) + { + bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(b); + + var menuItem = new ToggleMenuItem(b.Version ?? "(unnamed)") { State = { Value = isCurrentDifficulty }, }; + + if (!isCurrentDifficulty) + { + menuItem.Action.Value = () => + { + game?.PerformFromScreen(screen => + { + var osuScreen = (OsuScreen)screen; + osuScreen.Beatmap.Value = beatmapManager.GetWorkingBeatmap(b); + screen.Push(new Editor()); + }, new[] { typeof(MainMenu), typeof(SongSelect) }); + }; + } + + return menuItem; + } + public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); From fe2520c599b59e5f66e0aefba32c167c98f72052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 16:59:28 +0200 Subject: [PATCH 38/75] Add intermediary screen to avoid going back to menus --- osu.Game/Screens/Edit/Editor.cs | 21 +++++++++++++++------ osu.Game/Screens/Edit/EditorLoader.cs | 27 +++++++++++++++++++++++++++ osu.Game/Screens/Menu/MainMenu.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 4 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorLoader.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c7ac232f58..bc6d26135b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -35,9 +36,7 @@ using osu.Game.Screens.Edit.Design; using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Verify; -using osu.Game.Screens.Menu; using osu.Game.Screens.Play; -using osu.Game.Screens.Select; using osu.Game.Users; using osuTK.Graphics; using osuTK.Input; @@ -77,6 +76,9 @@ namespace osu.Game.Screens.Edit private Container screenContainer; + [CanBeNull] + private readonly EditorLoader loader; + private EditorScreen currentScreen; private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); @@ -106,6 +108,11 @@ namespace osu.Game.Screens.Edit [Resolved(CanBeNull = true)] private OsuGame game { get; set; } + public Editor(EditorLoader loader = null) + { + this.loader = loader; + } + [BackgroundDependencyLoader] private void load(OsuColour colours, OsuConfigManager config) { @@ -730,12 +737,14 @@ namespace osu.Game.Screens.Edit { menuItem.Action.Value = () => { + if (loader != null) + loader.ValidForResume = true; + game?.PerformFromScreen(screen => { - var osuScreen = (OsuScreen)screen; - osuScreen.Beatmap.Value = beatmapManager.GetWorkingBeatmap(b); - screen.Push(new Editor()); - }, new[] { typeof(MainMenu), typeof(SongSelect) }); + if (screen != null && screen == loader) + loader.PushEditor(); + }, new[] { typeof(EditorLoader) }); }; } diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs new file mode 100644 index 0000000000..5c8d0f5b16 --- /dev/null +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Screens; +using osu.Game.Screens.Play; + +namespace osu.Game.Screens.Edit +{ + /// + /// Transition screen for the editor. + /// Used to avoid backing out to main menu/song select when switching difficulties from within the editor. + /// + public class EditorLoader : ScreenWithBeatmapBackground + { + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + PushEditor(); + } + + public void PushEditor() + { + this.Push(new Editor(this)); + ValidForResume = false; + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 1d0182a945..8b2ec43e3e 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Menu OnEdit = delegate { Beatmap.SetDefault(); - this.Push(new Editor()); + this.Push(new EditorLoader()); }, OnSolo = loadSoloSongSelect, OnMultiplayer = () => this.Push(new Multiplayer()), diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b3d715e580..f11f9fd614 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -349,7 +349,7 @@ namespace osu.Game.Screens.Select throw new InvalidOperationException($"Attempted to edit when {nameof(AllowEditing)} is disabled"); Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap ?? beatmapNoDebounce); - this.Push(new Editor()); + this.Push(new EditorLoader()); } /// From c397cc2027ceb14e216daa075263e2f52588de0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 17:26:09 +0200 Subject: [PATCH 39/75] Restructure proof of concept --- .../Components/Menus/DifficultyMenuItem.cs | 24 +++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 30 ++++++++----------- osu.Game/Screens/Edit/EditorLoader.cs | 11 ++++++- 3 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs diff --git a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs new file mode 100644 index 0000000000..74b18f63ab --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs @@ -0,0 +1,24 @@ +// 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 osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Components.Menus +{ + public class DifficultyMenuItem : StatefulMenuItem + { + public DifficultyMenuItem(BeatmapInfo beatmapInfo, bool selected, Action difficultyChangeFunc) + : base(beatmapInfo.Version ?? "(unnamed)", null) + { + State.Value = selected; + + if (!selected) + Action.Value = () => difficultyChangeFunc.Invoke(beatmapInfo); + } + + public override IconUsage? GetIconForState(bool state) => state ? (IconUsage?)FontAwesome.Solid.Check : null; + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bc6d26135b..f30f92b602 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -727,28 +727,24 @@ namespace osu.Game.Screens.Edit return fileMenuItems; } - private ToggleMenuItem createDifficultyMenuItem(BeatmapInfo b) + private DifficultyMenuItem createDifficultyMenuItem(BeatmapInfo beatmapInfo) { - bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(b); + bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmapInfo); + return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, switchToDifficulty); + } - var menuItem = new ToggleMenuItem(b.Version ?? "(unnamed)") { State = { Value = isCurrentDifficulty }, }; + private void switchToDifficulty(BeatmapInfo beatmapInfo) + { + if (loader != null) + loader.ValidForResume = true; - if (!isCurrentDifficulty) + game?.PerformFromScreen(screen => { - menuItem.Action.Value = () => - { - if (loader != null) - loader.ValidForResume = true; + if (screen == null || screen != loader) + return; - game?.PerformFromScreen(screen => - { - if (screen != null && screen == loader) - loader.PushEditor(); - }, new[] { typeof(EditorLoader) }); - }; - } - - return menuItem; + loader.PushEditor(beatmapInfo); + }, new[] { typeof(EditorLoader) }); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 5c8d0f5b16..45f2d1bffd 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -1,7 +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 JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit @@ -12,14 +15,20 @@ namespace osu.Game.Screens.Edit /// public class EditorLoader : ScreenWithBeatmapBackground { + [Resolved] + private BeatmapManager beatmapManager { get; set; } + public override void OnEntering(IScreen last) { base.OnEntering(last); PushEditor(); } - public void PushEditor() + public void PushEditor([CanBeNull] BeatmapInfo beatmapInfo = null) { + if (beatmapInfo != null) + Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + this.Push(new Editor(this)); ValidForResume = false; } From a9403b65b3eccda6ac7f14826bb90efff5c71555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 17:53:57 +0200 Subject: [PATCH 40/75] Eliminate dependency on OsuGame --- osu.Game/Screens/Edit/Editor.cs | 17 +++++------------ osu.Game/Screens/Edit/EditorLoader.cs | 4 ++-- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f30f92b602..8e845bac05 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -105,9 +105,6 @@ namespace osu.Game.Screens.Edit [Resolved] private MusicController music { get; set; } - [Resolved(CanBeNull = true)] - private OsuGame game { get; set; } - public Editor(EditorLoader loader = null) { this.loader = loader; @@ -735,16 +732,12 @@ namespace osu.Game.Screens.Edit private void switchToDifficulty(BeatmapInfo beatmapInfo) { - if (loader != null) - loader.ValidForResume = true; + if (loader == null) + return; - game?.PerformFromScreen(screen => - { - if (screen == null || screen != loader) - return; - - loader.PushEditor(beatmapInfo); - }, new[] { typeof(EditorLoader) }); + loader.ValidForResume = true; + this.Exit(); + loader.PushEditor(beatmapInfo); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 45f2d1bffd..011f40419c 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -24,13 +24,13 @@ namespace osu.Game.Screens.Edit PushEditor(); } - public void PushEditor([CanBeNull] BeatmapInfo beatmapInfo = null) + public void PushEditor([CanBeNull] BeatmapInfo beatmapInfo = null) => Schedule(() => { if (beatmapInfo != null) Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); this.Push(new Editor(this)); ValidForResume = false; - } + }); } } From c72523bc14afe2b96ad0fb3a298b0edc9d8f5a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 18:32:38 +0200 Subject: [PATCH 41/75] Add basic test for difficulty switching --- .../Editing/TestSceneDifficultySwitching.cs | 91 +++++++++++++++++++ .../Components/Menus/DifficultyMenuItem.cs | 3 + 2 files changed, 94 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs new file mode 100644 index 0000000000..4ac2e9cfef --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -0,0 +1,91 @@ +// 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.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.Menus; +using osu.Game.Tests.Beatmaps.IO; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneDifficultySwitching : ScreenTestScene + { + private BeatmapSetInfo importedBeatmapSet; + + [Resolved] + private OsuGameBase game { get; set; } + + private Editor editor; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [SetUpSteps] + public void SetUp() + { + AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result); + + AddStep("set current beatmap", () => Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First())); + AddStep("push loader", () => Stack.Push(new EditorLoader())); + + AddUntilStep("wait for editor to load", () => Stack.CurrentScreen is Editor); + AddStep("store editor", () => editor = (Editor)Stack.CurrentScreen); + } + + [Test] + public void TestBasicSwitch() + { + BeatmapInfo targetDifficulty = null; + + AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); + switchToDifficulty(() => targetDifficulty); + confirmEditingBeatmap(() => targetDifficulty); + + AddStep("exit editor", () => Stack.Exit()); + // ensure editor loader didn't resume. + AddAssert("stack empty", () => Stack.CurrentScreen == null); + } + + private void switchToDifficulty(Func difficulty) + { + AddUntilStep("wait for menubar to load", () => editor.ChildrenOfType().Any()); + AddStep("open file menu", () => + { + var menuBar = editor.ChildrenOfType().Single(); + var fileMenu = menuBar.ChildrenOfType().First(); + InputManager.MoveMouseTo(fileMenu); + InputManager.Click(MouseButton.Left); + }); + + AddStep("open difficulty menu", () => + { + var difficultySelector = + editor.ChildrenOfType().Single(item => item.Item.Text.Value.ToString().Contains("Change difficulty")); + InputManager.MoveMouseTo(difficultySelector); + }); + AddWaitStep("wait for open", 3); + + AddStep("switch to target difficulty", () => + { + var difficultyMenuItem = + editor.ChildrenOfType() + .Last(item => item.Item is DifficultyMenuItem difficultyItem && difficultyItem.Beatmap.Equals(difficulty.Invoke())); + InputManager.MoveMouseTo(difficultyMenuItem); + InputManager.Click(MouseButton.Left); + }); + } + + private void confirmEditingBeatmap(Func targetDifficulty) + { + AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke())); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs index 74b18f63ab..5f9b72447b 100644 --- a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs +++ b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs @@ -10,9 +10,12 @@ namespace osu.Game.Screens.Edit.Components.Menus { public class DifficultyMenuItem : StatefulMenuItem { + public BeatmapInfo Beatmap { get; } + public DifficultyMenuItem(BeatmapInfo beatmapInfo, bool selected, Action difficultyChangeFunc) : base(beatmapInfo.Version ?? "(unnamed)", null) { + Beatmap = beatmapInfo; State.Value = selected; if (!selected) From 382269b362061da851565b88092c08b923eeadbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 19:11:46 +0200 Subject: [PATCH 42/75] Test staying on same difficulty due to unsaved changes --- .../Editing/TestSceneDifficultySwitching.cs | 37 ++++++++++++++++++- osu.Game/Screens/Edit/Editor.cs | 13 ++++++- osu.Game/Screens/Edit/EditorLoader.cs | 25 ++++++++++--- osu.Game/Screens/Edit/PromptForSaveDialog.cs | 3 +- 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 4ac2e9cfef..60c2518a7e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Dialog; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Tests.Beatmaps.IO; @@ -35,8 +36,9 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set current beatmap", () => Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First())); AddStep("push loader", () => Stack.Push(new EditorLoader())); - AddUntilStep("wait for editor to load", () => Stack.CurrentScreen is Editor); + AddUntilStep("wait for editor push", () => Stack.CurrentScreen is Editor); AddStep("store editor", () => editor = (Editor)Stack.CurrentScreen); + AddUntilStep("wait for editor to load", () => editor.IsLoaded); } [Test] @@ -53,6 +55,39 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("stack empty", () => Stack.CurrentScreen == null); } + [Test] + public void TestPreventSwitchDueToUnsavedChanges() + { + BeatmapInfo targetDifficulty = null; + PromptForSaveDialog saveDialog = null; + + AddStep("remove first hitobject", () => + { + var editorBeatmap = editor.ChildrenOfType().Single(); + editorBeatmap.RemoveAt(0); + }); + + AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); + switchToDifficulty(() => targetDifficulty); + + AddUntilStep("prompt for save dialog shown", () => + { + saveDialog = this.ChildrenOfType().Single(); + return saveDialog != null; + }); + AddStep("continue editing", () => + { + var continueButton = saveDialog.ChildrenOfType().Last(); + continueButton.TriggerClick(); + }); + + confirmEditingBeatmap(() => importedBeatmapSet.Beatmaps.First()); + + AddRepeatStep("exit editor forcefully", () => Stack.Exit(), 2); + // ensure editor loader didn't resume. + AddAssert("stack empty", () => Stack.CurrentScreen == null); + } + private void switchToDifficulty(Func difficulty) { AddUntilStep("wait for menubar to load", () => editor.ChildrenOfType().Any()); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8e845bac05..df08e0a017 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -498,7 +498,7 @@ namespace osu.Game.Screens.Edit if (isNewBeatmap || HasUnsavedChanges) { - dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); + dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave, cancelPendingDifficultySwitch)); return true; } } @@ -737,7 +737,16 @@ namespace osu.Game.Screens.Edit loader.ValidForResume = true; this.Exit(); - loader.PushEditor(beatmapInfo); + loader.ScheduleDifficultySwitch(beatmapInfo); + } + + private void cancelPendingDifficultySwitch() + { + if (loader == null) + return; + + loader.ValidForResume = false; + loader.CancelDifficultySwitch(); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 011f40419c..d913076e83 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -4,6 +4,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Screens.Play; @@ -18,19 +19,31 @@ namespace osu.Game.Screens.Edit [Resolved] private BeatmapManager beatmapManager { get; set; } + [CanBeNull] + private ScheduledDelegate scheduledDifficultySwitch; + public override void OnEntering(IScreen last) { base.OnEntering(last); - PushEditor(); + pushEditor(); } - public void PushEditor([CanBeNull] BeatmapInfo beatmapInfo = null) => Schedule(() => + private void pushEditor() { - if (beatmapInfo != null) - Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); - this.Push(new Editor(this)); ValidForResume = false; - }); + } + + public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo) + { + CancelDifficultySwitch(); + scheduledDifficultySwitch = Schedule(() => + { + Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + pushEditor(); + }); + } + + public void CancelDifficultySwitch() => scheduledDifficultySwitch?.Cancel(); } } diff --git a/osu.Game/Screens/Edit/PromptForSaveDialog.cs b/osu.Game/Screens/Edit/PromptForSaveDialog.cs index 16504b47bd..e308a9533d 100644 --- a/osu.Game/Screens/Edit/PromptForSaveDialog.cs +++ b/osu.Game/Screens/Edit/PromptForSaveDialog.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.Edit { public class PromptForSaveDialog : PopupDialog { - public PromptForSaveDialog(Action exit, Action saveAndExit) + public PromptForSaveDialog(Action exit, Action saveAndExit, Action cancel) { HeaderText = "Did you want to save your changes?"; @@ -30,6 +30,7 @@ namespace osu.Game.Screens.Edit new PopupDialogCancelButton { Text = @"Oops, continue editing", + Action = cancel }, }; } From 74a129dc27fd3c0bc6ce6e5919106f7a1b9e45ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 19:20:18 +0200 Subject: [PATCH 43/75] Test switching difficulties after discarding changes --- .../Editing/TestSceneDifficultySwitching.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 60c2518a7e..ecf1fec850 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -88,6 +88,39 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("stack empty", () => Stack.CurrentScreen == null); } + [Test] + public void TestAllowSwitchAfterDiscardingUnsavedChanges() + { + BeatmapInfo targetDifficulty = null; + PromptForSaveDialog saveDialog = null; + + AddStep("remove first hitobject", () => + { + var editorBeatmap = editor.ChildrenOfType().Single(); + editorBeatmap.RemoveAt(0); + }); + + AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); + switchToDifficulty(() => targetDifficulty); + + AddUntilStep("prompt for save dialog shown", () => + { + saveDialog = this.ChildrenOfType().Single(); + return saveDialog != null; + }); + AddStep("discard changes", () => + { + var continueButton = saveDialog.ChildrenOfType().Single(); + continueButton.TriggerClick(); + }); + + confirmEditingBeatmap(() => targetDifficulty); + + AddStep("exit editor forcefully", () => Stack.Exit()); + // ensure editor loader didn't resume. + AddAssert("stack empty", () => Stack.CurrentScreen == null); + } + private void switchToDifficulty(Func difficulty) { AddUntilStep("wait for menubar to load", () => editor.ChildrenOfType().Any()); From 7012a1d9340a01011cbc0e3ef190f3f0ec8030d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 20:24:07 +0200 Subject: [PATCH 44/75] Fix issues with main menu -> editor loader transition --- .../Editing/TestSceneDifficultySwitching.cs | 15 +++++++++++-- osu.Game/Screens/Edit/EditorLoader.cs | 22 ++++++++++++++----- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index ecf1fec850..0f3d413a7d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Dialog; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Menus; +using osu.Game.Screens.Menu; using osu.Game.Tests.Beatmaps.IO; using osuTK.Input; @@ -19,15 +20,25 @@ namespace osu.Game.Tests.Visual.Editing public class TestSceneDifficultySwitching : ScreenTestScene { private BeatmapSetInfo importedBeatmapSet; + private Editor editor; + + // required for screen transitions to work properly + // (see comment in EditorLoader.LogoArriving). + [Cached] + private OsuLogo logo = new OsuLogo + { + Alpha = 0 + }; [Resolved] private OsuGameBase game { get; set; } - private Editor editor; - [Resolved] private BeatmapManager beatmaps { get; set; } + [BackgroundDependencyLoader] + private void load() => Add(logo); + [SetUpSteps] public void SetUp() { diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index d913076e83..07eb5e5b1b 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -3,10 +3,11 @@ using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; -using osu.Game.Screens.Play; +using osu.Game.Screens.Menu; namespace osu.Game.Screens.Edit { @@ -14,18 +15,29 @@ namespace osu.Game.Screens.Edit /// Transition screen for the editor. /// Used to avoid backing out to main menu/song select when switching difficulties from within the editor. /// - public class EditorLoader : ScreenWithBeatmapBackground + public class EditorLoader : OsuScreen { + public override float BackgroundParallaxAmount => 0.1f; + + public override bool AllowBackButton => false; + + public override bool HideOverlaysOnEnter => true; + + public override bool DisallowExternalBeatmapRulesetChanges => true; + [Resolved] private BeatmapManager beatmapManager { get; set; } [CanBeNull] private ScheduledDelegate scheduledDifficultySwitch; - public override void OnEntering(IScreen last) + protected override void LogoArriving(OsuLogo logo, bool resuming) { - base.OnEntering(last); - pushEditor(); + base.LogoArriving(logo, resuming); + // the push cannot happen in OnEntering() or similar (even if scheduled), because the transition from main menu will look bad. + // that is because this screen pushing the editor makes it no longer current, and OsuScreen checks if the screen is current + // before enqueueing this screen's LogoArriving onto the logo animation sequence. + logo.Delay(300).Schedule(pushEditor); } private void pushEditor() From d6a47fd99ceeb2f4a707da8c11ed66c940fc787e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Sep 2021 21:00:19 +0200 Subject: [PATCH 45/75] Sort difficulties by ruleset and star rating in menu --- osu.Game/Screens/Edit/Editor.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index df08e0a017..7d0b936df9 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -715,7 +715,17 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItemSpacer()); var beatmapSet = beatmapManager.QueryBeatmapSet(bs => bs.ID == Beatmap.Value.BeatmapSetInfo.ID) ?? playableBeatmap.BeatmapInfo.BeatmapSet; - var difficultyItems = beatmapSet.Beatmaps.Select(createDifficultyMenuItem).ToList(); + + var difficultyItems = new List(); + + foreach (var rulesetBeatmaps in beatmapSet.Beatmaps.GroupBy(b => b.RulesetID).OrderBy(group => group.Key)) + { + if (difficultyItems.Count > 0) + difficultyItems.Add(new EditorMenuItemSpacer()); + + foreach (var beatmap in rulesetBeatmaps.OrderBy(b => b.StarDifficulty)) + difficultyItems.Add(createDifficultyMenuItem(beatmap)); + } fileMenuItems.Add(new EditorMenuItem("Change difficulty") { Items = difficultyItems }); From 458cde832da5a75b742fb808637e559de62fad3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Sep 2021 14:09:40 +0900 Subject: [PATCH 46/75] Avoid using SSDQ for validity computation --- .../Settings/TestSceneTabletSettings.cs | 14 +----- .../Sections/Input/TabletAreaSelection.cs | 44 ++++++++++++------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 49d6b7033e..2486abdd7a 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -1,11 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Testing; using osu.Framework.Utils; @@ -93,17 +91,9 @@ namespace osu.Game.Tests.Visual.Settings ensureValid(); } - private void ensureValid() - { - AddUntilStep("wait for transforms", () => settings.AreaSelection.ChildrenOfType().All(c => !c.Transforms.Any())); - AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); - } + private void ensureValid() => AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds); - private void ensureInvalid() - { - AddUntilStep("wait for transforms", () => settings.AreaSelection.ChildrenOfType().All(c => !c.Transforms.Any())); - AddAssert("area invalid", () => !settings.AreaSelection.IsWithinBounds); - } + private void ensureInvalid() => AddAssert("area invalid", () => !settings.AreaSelection.IsWithinBounds); public class TestTabletHandler : ITabletHandler { diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index d12052b24d..58abfab29c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -114,29 +114,30 @@ namespace osu.Game.Overlays.Settings.Sections.Input areaOffset.BindTo(handler.AreaOffset); areaOffset.BindValueChanged(val => { - usableAreaContainer.MoveTo(val.NewValue, 100, Easing.OutQuint) - .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + usableAreaContainer.MoveTo(val.NewValue, 100, Easing.OutQuint); + checkBounds(); }, true); areaSize.BindTo(handler.AreaSize); areaSize.BindValueChanged(val => { - usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint) - .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint); int x = (int)val.NewValue.X; int y = (int)val.NewValue.Y; int commonDivider = greatestCommonDivider(x, y); usableAreaText.Text = $"{(float)x / commonDivider}:{(float)y / commonDivider}"; + checkBounds(); }, true); rotation.BindTo(handler.Rotation); rotation.BindValueChanged(val => { - usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint) - .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint); tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint); + + checkBounds(); }, true); tablet.BindTo(handler.Tablet); @@ -174,22 +175,33 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (tablet.Value == null) return; - // All of this manual logic is just to get around floating point issues when doing a contains check on the screen quads. - // This is best effort, as it's only used for display purposes. If we need for anything more, manual math on the raw values should be preferred. - var containerQuad = tabletContainer.ScreenSpaceDrawQuad.AABBFloat.Inflate(1); - var usableAreaQuad = Quad.FromRectangle(usableAreaContainer.ScreenSpaceDrawQuad.AABBFloat); + // allow for some degree of floating point error, as we don't care about being perfect here. + const float lenience = 0.5f; + + var tabletArea = new Quad(-lenience, -lenience, tablet.Value.Size.X + lenience * 2, tablet.Value.Size.Y + lenience * 2); + + var halfUsableArea = areaSize.Value / 2; + var offset = areaOffset.Value; + + var usableAreaQuad = new Quad( + new Vector2(-halfUsableArea.X, -halfUsableArea.Y), + new Vector2(halfUsableArea.X, -halfUsableArea.Y), + new Vector2(-halfUsableArea.X, halfUsableArea.Y), + new Vector2(halfUsableArea.X, halfUsableArea.Y) + ); var matrix = Matrix3.Identity; - MatrixExtensions.TranslateFromLeft(ref matrix, usableAreaQuad.Centre); + + MatrixExtensions.TranslateFromLeft(ref matrix, offset); MatrixExtensions.RotateFromLeft(ref matrix, MathUtils.DegreesToRadians(rotation.Value)); - MatrixExtensions.TranslateFromLeft(ref matrix, -usableAreaQuad.Centre); + usableAreaQuad *= matrix; IsWithinBounds = - containerQuad.Contains(usableAreaQuad.TopLeft) && - containerQuad.Contains(usableAreaQuad.TopRight) && - containerQuad.Contains(usableAreaQuad.BottomLeft) && - containerQuad.Contains(usableAreaQuad.BottomRight); + tabletArea.Contains(usableAreaQuad.TopLeft) && + tabletArea.Contains(usableAreaQuad.TopRight) && + tabletArea.Contains(usableAreaQuad.BottomLeft) && + tabletArea.Contains(usableAreaQuad.BottomRight); usableFill.FadeColour(IsWithinBounds ? colour.Blue : colour.RedLight, 100); } From 6f482c3602b3fd2c6801878e0fcb368ba5dd1633 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Sep 2021 14:14:42 +0900 Subject: [PATCH 47/75] Add test coverage of sharper aspect ratio --- .../Settings/TestSceneTabletSettings.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 2486abdd7a..997eac709d 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.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.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -8,6 +9,7 @@ using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Overlays; +using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections.Input; using osuTK; @@ -51,6 +53,27 @@ namespace osu.Game.Tests.Visual.Settings AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(Vector2.Zero)); } + [Test] + public void TestWideAspectRatioValidity() + { + AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100))); + + AddStep("Reset to full area", () => settings.ChildrenOfType().First().TriggerClick()); + ensureValid(); + + AddStep("rotate 10", () => tabletHandler.Rotation.Value = 10); + ensureInvalid(); + + AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f); + ensureInvalid(); + + AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f); + ensureInvalid(); + + AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f); + ensureValid(); + } + [Test] public void TestRotationValidity() { From 1c4a3c584a76fb9ecbf7b56d6d62850fcc89b88d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Sep 2021 15:04:27 +0900 Subject: [PATCH 48/75] Use correct lookup type to ensure username based lookups always prefer username --- osu.Game/Online/API/Requests/GetUserRequest.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 281926c096..e49c4ab298 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -8,8 +8,9 @@ namespace osu.Game.Online.API.Requests { public class GetUserRequest : APIRequest { - private readonly string userIdentifier; + private readonly string lookup; public readonly RulesetInfo Ruleset; + private readonly LookupType lookupType; /// /// Gets the currently logged-in user. @@ -25,7 +26,8 @@ namespace osu.Game.Online.API.Requests /// The ruleset to get the user's info for. public GetUserRequest(long? userId = null, RulesetInfo ruleset = null) { - this.userIdentifier = userId.ToString(); + lookup = userId.ToString(); + lookupType = LookupType.Id; Ruleset = ruleset; } @@ -36,10 +38,17 @@ namespace osu.Game.Online.API.Requests /// The ruleset to get the user's info for. public GetUserRequest(string username = null, RulesetInfo ruleset = null) { - this.userIdentifier = username; + lookup = username; + lookupType = LookupType.Username; Ruleset = ruleset; } - protected override string Target => userIdentifier != null ? $@"users/{userIdentifier}/{Ruleset?.ShortName}" : $@"me/{Ruleset?.ShortName}"; + protected override string Target => lookup != null ? $@"users/{lookup}/{Ruleset?.ShortName}?k={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}"; + + private enum LookupType + { + Id, + Username + } } } From 62d65f81fb785ae4656b312f6617f469401349d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Sep 2021 16:53:09 +0900 Subject: [PATCH 49/75] Limit at 10000 entries --- .github/workflows/test-diffcalc.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 3bec11928f..d963e49d9f 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -156,6 +156,7 @@ jobs: AND m.mods = p.mods WHERE abs(m.diff_unified - p.diff_unified) > 0.1 ORDER BY abs(m.diff_unified - p.diff_unified) - DESC;" + DESC + LIMIT 10000;" # Todo: Run ppcalc \ No newline at end of file From 401d38fc051366cb26f3f25bf389decbf5a5bfec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Sep 2021 19:07:28 +0900 Subject: [PATCH 50/75] Fix possible nullref --- osu.Game/Screens/Ranking/ScorePanelList.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 711e0d60ec..4ac30ef4ed 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -301,6 +301,9 @@ namespace osu.Game.Screens.Ranking protected override bool OnKeyDown(KeyDownEvent e) { + if (expandedPanel == null) + return base.OnKeyDown(e); + var expandedPanelIndex = flow.GetPanelIndex(expandedPanel.Score); switch (e.Key) From 20100b8894d5a58af13e1351628e0366e6256ac5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Sep 2021 20:20:52 +0900 Subject: [PATCH 51/75] Fix a few test failures --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 2 ++ osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 8 +++++--- osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs | 5 +++++ .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 4 ++-- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 ++ 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 61d49e4018..b826683cd5 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -160,6 +160,8 @@ namespace osu.Game.Tests.Visual.Playlists Ruleset = { Value = new OsuRuleset().RulesetInfo } })); }); + + AddUntilStep("wait for load", () => resultsScreen.ChildrenOfType().FirstOrDefault()?.AllPanelsVisible == true); } private void waitForDisplay() diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index ba6b6bd529..34a9610804 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Ranking TestResultsScreen screen = null; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); - AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); AddStep("click expanded panel", () => { @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Ranking TestResultsScreen screen = null; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); - AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); AddStep("click expanded panel", () => { @@ -177,7 +177,7 @@ namespace osu.Game.Tests.Visual.Ranking TestResultsScreen screen = null; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); - AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); ScorePanel expandedPanel = null; ScorePanel contractedPanel = null; @@ -201,6 +201,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestFetchScoresAfterShowingStatistics() + { DelayedFetchResultsScreen screen = null; @@ -223,6 +224,7 @@ namespace osu.Game.Tests.Visual.Ranking TestResultsScreen screen = null; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + AddUntilStep("wait for load", () => this.ChildrenOfType().Single().AllPanelsVisible); AddAssert("download button is disabled", () => !screen.ChildrenOfType().Last().Enabled.Value); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index f330e99d55..b2585c0509 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -159,6 +159,9 @@ namespace osu.Game.Tests.Visual.Ranking var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + firstScore.User.Username = "A"; + secondScore.User.Username = "B"; + createListStep(() => new ScorePanelList()); AddStep("add scores and select first", () => @@ -168,6 +171,8 @@ namespace osu.Game.Tests.Visual.Ranking list.SelectedScore.Value = firstScore; }); + AddUntilStep("wait for load", () => list.AllPanelsVisible); + assertScoreState(firstScore, true); assertScoreState(secondScore, false); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 98482601ee..8efee2723f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -158,14 +158,14 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID)); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s != scores[0])); } [Test] public void TestDeleteViaDatabase() { AddStep("delete top score", () => scoreManager.Delete(scores[0])); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID)); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s != scores[0])); } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 4ac30ef4ed..6d65137984 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -47,6 +47,8 @@ namespace osu.Game.Screens.Ranking /// public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance); + public bool AllPanelsVisible => flow.All(p => p.IsPresent); + /// /// The current scroll position. /// From 2a5b857f10f51f0d5e2cd3666be3e08db9ed8338 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 00:45:53 +0900 Subject: [PATCH 52/75] Avoid loading unnecessary fonts in headless testing --- osu.Game/OsuGameBase.cs | 55 +++++++++++++++------------ osu.Game/Tests/Visual/OsuTestScene.cs | 5 +++ 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 762216e93c..a63e59f3d3 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -205,31 +205,7 @@ namespace osu.Game dependencies.CacheAs(this); dependencies.CacheAs(LocalConfig); - AddFont(Resources, @"Fonts/osuFont"); - - AddFont(Resources, @"Fonts/Torus/Torus-Regular"); - AddFont(Resources, @"Fonts/Torus/Torus-Light"); - AddFont(Resources, @"Fonts/Torus/Torus-SemiBold"); - AddFont(Resources, @"Fonts/Torus/Torus-Bold"); - - AddFont(Resources, @"Fonts/Inter/Inter-Regular"); - AddFont(Resources, @"Fonts/Inter/Inter-RegularItalic"); - AddFont(Resources, @"Fonts/Inter/Inter-Light"); - AddFont(Resources, @"Fonts/Inter/Inter-LightItalic"); - AddFont(Resources, @"Fonts/Inter/Inter-SemiBold"); - AddFont(Resources, @"Fonts/Inter/Inter-SemiBoldItalic"); - AddFont(Resources, @"Fonts/Inter/Inter-Bold"); - AddFont(Resources, @"Fonts/Inter/Inter-BoldItalic"); - - AddFont(Resources, @"Fonts/Noto/Noto-Basic"); - AddFont(Resources, @"Fonts/Noto/Noto-Hangul"); - AddFont(Resources, @"Fonts/Noto/Noto-CJK-Basic"); - AddFont(Resources, @"Fonts/Noto/Noto-CJK-Compatibility"); - AddFont(Resources, @"Fonts/Noto/Noto-Thai"); - - AddFont(Resources, @"Fonts/Venera/Venera-Light"); - AddFont(Resources, @"Fonts/Venera/Venera-Bold"); - AddFont(Resources, @"Fonts/Venera/Venera-Black"); + InitialiseFonts(); Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY; @@ -368,6 +344,35 @@ namespace osu.Game Ruleset.BindValueChanged(onRulesetChanged); } + protected virtual void InitialiseFonts() + { + AddFont(Resources, @"Fonts/osuFont"); + + AddFont(Resources, @"Fonts/Torus/Torus-Regular"); + AddFont(Resources, @"Fonts/Torus/Torus-Light"); + AddFont(Resources, @"Fonts/Torus/Torus-SemiBold"); + AddFont(Resources, @"Fonts/Torus/Torus-Bold"); + + AddFont(Resources, @"Fonts/Inter/Inter-Regular"); + AddFont(Resources, @"Fonts/Inter/Inter-RegularItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-Light"); + AddFont(Resources, @"Fonts/Inter/Inter-LightItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-SemiBold"); + AddFont(Resources, @"Fonts/Inter/Inter-SemiBoldItalic"); + AddFont(Resources, @"Fonts/Inter/Inter-Bold"); + AddFont(Resources, @"Fonts/Inter/Inter-BoldItalic"); + + AddFont(Resources, @"Fonts/Noto/Noto-Basic"); + AddFont(Resources, @"Fonts/Noto/Noto-Hangul"); + AddFont(Resources, @"Fonts/Noto/Noto-CJK-Basic"); + AddFont(Resources, @"Fonts/Noto/Noto-CJK-Compatibility"); + AddFont(Resources, @"Fonts/Noto/Noto-Thai"); + + AddFont(Resources, @"Fonts/Venera/Venera-Light"); + AddFont(Resources, @"Fonts/Venera/Venera-Bold"); + AddFont(Resources, @"Fonts/Venera/Venera-Black"); + } + private IDisposable blocking; private void updateThreadStateChanged(ValueChangedEvent state) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index ef9181c8a6..03434961ea 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -367,6 +367,11 @@ namespace osu.Game.Tests.Visual Add(runner = new TestSceneTestRunner.TestRunner()); } + protected override void InitialiseFonts() + { + // skip fonts load as it's not required for testing purposes. + } + public void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test); } } From bd7d6dd35d54ca11c11616ea1b5f97a74e87b2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Sep 2021 21:27:17 +0200 Subject: [PATCH 53/75] Rename method --- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7d0b936df9..d0b50c13e6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -498,7 +498,7 @@ namespace osu.Game.Screens.Edit if (isNewBeatmap || HasUnsavedChanges) { - dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave, cancelPendingDifficultySwitch)); + dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave, cancelExit)); return true; } } @@ -750,7 +750,7 @@ namespace osu.Game.Screens.Edit loader.ScheduleDifficultySwitch(beatmapInfo); } - private void cancelPendingDifficultySwitch() + private void cancelExit() { if (loader == null) return; From 2d59008f528f441cff81e9144b25bf208da04173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Sep 2021 21:30:50 +0200 Subject: [PATCH 54/75] Move screen management logic to `EditorLoader` --- osu.Game/Screens/Edit/Editor.cs | 19 ++----------------- osu.Game/Screens/Edit/EditorLoader.cs | 11 +++++++++-- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d0b50c13e6..1b9a94da58 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -740,24 +740,9 @@ namespace osu.Game.Screens.Edit return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, switchToDifficulty); } - private void switchToDifficulty(BeatmapInfo beatmapInfo) - { - if (loader == null) - return; + private void switchToDifficulty(BeatmapInfo beatmapInfo) => loader?.ScheduleDifficultySwitch(beatmapInfo); - loader.ValidForResume = true; - this.Exit(); - loader.ScheduleDifficultySwitch(beatmapInfo); - } - - private void cancelExit() - { - if (loader == null) - return; - - loader.ValidForResume = false; - loader.CancelDifficultySwitch(); - } + private void cancelExit() => loader?.CancelPendingDifficultySwitch(); public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 07eb5e5b1b..fbb358d217 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -48,7 +48,10 @@ namespace osu.Game.Screens.Edit public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo) { - CancelDifficultySwitch(); + scheduledDifficultySwitch?.Cancel(); + ValidForResume = true; + + this.MakeCurrent(); scheduledDifficultySwitch = Schedule(() => { Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); @@ -56,6 +59,10 @@ namespace osu.Game.Screens.Edit }); } - public void CancelDifficultySwitch() => scheduledDifficultySwitch?.Cancel(); + public void CancelPendingDifficultySwitch() + { + scheduledDifficultySwitch?.Cancel(); + ValidForResume = false; + } } } From 5b9f37702b72e69e1976e6091bb8754650149135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Sep 2021 21:31:59 +0200 Subject: [PATCH 55/75] Remove unnecessary delay before pushing editor from loader --- osu.Game/Screens/Edit/EditorLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index fbb358d217..92420fa7c0 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -3,7 +3,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -34,10 +33,11 @@ namespace osu.Game.Screens.Edit protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); + // the push cannot happen in OnEntering() or similar (even if scheduled), because the transition from main menu will look bad. // that is because this screen pushing the editor makes it no longer current, and OsuScreen checks if the screen is current // before enqueueing this screen's LogoArriving onto the logo animation sequence. - logo.Delay(300).Schedule(pushEditor); + pushEditor(); } private void pushEditor() From c9325cc41947c5c1b62afe5b3bc249b643f5527b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Sep 2021 14:15:23 +0900 Subject: [PATCH 56/75] Fix results screen test scene --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 1 + osu.Game/Screens/Ranking/ScorePanelList.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index b826683cd5..4bcc887b9f 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -167,6 +167,7 @@ namespace osu.Game.Tests.Visual.Playlists private void waitForDisplay() { AddUntilStep("wait for request to complete", () => requestComplete); + AddUntilStep("wait for panels to be visible", () => resultsScreen.ChildrenOfType().FirstOrDefault()?.AllPanelsVisible == true); AddWaitStep("wait for display", 5); } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 6d65137984..a847df344f 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -40,12 +40,12 @@ namespace osu.Game.Screens.Ranking /// /// Whether this can be scrolled and is currently scrolled to the start. /// - public bool IsScrolledToStart => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.Current <= scroll_endpoint_distance; + public bool IsScrolledToStart => flow.Count > 0 && AllPanelsVisible && scroll.ScrollableExtent > 0 && scroll.Current <= scroll_endpoint_distance; /// /// Whether this can be scrolled and is currently scrolled to the end. /// - public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance); + public bool IsScrolledToEnd => flow.Count > 0 && AllPanelsVisible && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance); public bool AllPanelsVisible => flow.All(p => p.IsPresent); From 59aa4dabfda755ffac0a5e9bb4a58d371994394a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 14:33:58 +0900 Subject: [PATCH 57/75] Improve code around background screen handling to read better --- osu.Game/Screens/BackgroundScreenStack.cs | 12 +++++++++--- osu.Game/Screens/OsuScreen.cs | 9 +++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 294f23d2ac..9f562a618e 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -17,15 +17,21 @@ namespace osu.Game.Screens Origin = Anchor.Centre; } - public void Push(BackgroundScreen screen) + /// + /// Attempt to push a new background screen to this stack. + /// + /// The screen to attempt to push. + /// Whether the push succeeded. For example, if the existing screen was already of the correct type this will return false. + public bool Push(BackgroundScreen screen) { if (screen == null) - return; + return false; if (EqualityComparer.Default.Equals((BackgroundScreen)CurrentScreen, screen)) - return; + return false; base.Push(screen); + return true; } } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index e3fe14a585..9aec2a5c19 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -186,17 +186,14 @@ namespace osu.Game.Screens { applyArrivingDefaults(false); - backgroundStack?.Push(ownedBackground = CreateBackground()); - - background = backgroundStack?.CurrentScreen as BackgroundScreen; - - if (background != ownedBackground) + if (backgroundStack?.Push(ownedBackground = CreateBackground()) != true) { - // background may have not been replaced, at which point we don't want to track the background lifetime. + // If the constructed instance was not actually pushed to the background stack, we don't want to track it unnecessarily. ownedBackground?.Dispose(); ownedBackground = null; } + background = backgroundStack?.CurrentScreen as BackgroundScreen; base.OnEntering(last); } From ddaa95a1ca37e1c264850c4a239e13e83c500cde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 14:34:18 +0900 Subject: [PATCH 58/75] Fix `pushEditor` function running twice on returning to loader --- osu.Game/Screens/Edit/EditorLoader.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 92420fa7c0..8798a7964b 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -34,10 +34,13 @@ namespace osu.Game.Screens.Edit { base.LogoArriving(logo, resuming); - // the push cannot happen in OnEntering() or similar (even if scheduled), because the transition from main menu will look bad. - // that is because this screen pushing the editor makes it no longer current, and OsuScreen checks if the screen is current - // before enqueueing this screen's LogoArriving onto the logo animation sequence. - pushEditor(); + if (!resuming) + { + // the push cannot happen in OnEntering() or similar (even if scheduled), because the transition from main menu will look bad. + // that is because this screen pushing the editor makes it no longer current, and OsuScreen checks if the screen is current + // before enqueueing this screen's LogoArriving onto the logo animation sequence. + pushEditor(); + } } private void pushEditor() From 7921ad451678925d6bf51628cc6015ac0c69c26b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 14:34:47 +0900 Subject: [PATCH 59/75] Add loading spinner in case load takes longer than expected --- osu.Game/Screens/Edit/EditorLoader.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 8798a7964b..499baeec4f 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Menu; namespace osu.Game.Screens.Edit @@ -43,10 +44,16 @@ namespace osu.Game.Screens.Edit } } - private void pushEditor() + [BackgroundDependencyLoader] + private void load() { - this.Push(new Editor(this)); - ValidForResume = false; + AddRangeInternal(new Drawable[] + { + new LoadingSpinner(true) + { + State = { Value = Visibility.Visible }, + } + }); } public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo) @@ -62,6 +69,12 @@ namespace osu.Game.Screens.Edit }); } + private void pushEditor() + { + this.Push(new Editor(this)); + ValidForResume = false; + } + public void CancelPendingDifficultySwitch() { scheduledDifficultySwitch?.Cancel(); From 9edd010b1d0a52dd233ea41f2e42ea7fb05b808e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 14:34:54 +0900 Subject: [PATCH 60/75] Fix unnecessary background screen transition --- osu.Game/Screens/Edit/EditorLoader.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 499baeec4f..aec7d32939 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -3,11 +3,14 @@ using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit { @@ -15,7 +18,7 @@ namespace osu.Game.Screens.Edit /// Transition screen for the editor. /// Used to avoid backing out to main menu/song select when switching difficulties from within the editor. /// - public class EditorLoader : OsuScreen + public class EditorLoader : ScreenWithBeatmapBackground { public override float BackgroundParallaxAmount => 0.1f; @@ -62,9 +65,16 @@ namespace osu.Game.Screens.Edit ValidForResume = true; this.MakeCurrent(); + scheduledDifficultySwitch = Schedule(() => { Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + + // This screen is a weird exception to the rule that nothing after song select changes the global beatmap. + // Because of this, we need to update the background stack's beatmap to match. + // If we don't do this, the editor will see a discrepancy and create a new background, along with an unnecessary transition. + ApplyToBackground(b => b.Beatmap = Beatmap.Value); + pushEditor(); }); } From 93da531d135890c7b9addf0570721fdf7e6573da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 14:33:58 +0900 Subject: [PATCH 61/75] Improve code around background screen handling to read better --- osu.Game/Screens/BackgroundScreenStack.cs | 12 +++++++++--- osu.Game/Screens/OsuScreen.cs | 9 +++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 294f23d2ac..9f562a618e 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -17,15 +17,21 @@ namespace osu.Game.Screens Origin = Anchor.Centre; } - public void Push(BackgroundScreen screen) + /// + /// Attempt to push a new background screen to this stack. + /// + /// The screen to attempt to push. + /// Whether the push succeeded. For example, if the existing screen was already of the correct type this will return false. + public bool Push(BackgroundScreen screen) { if (screen == null) - return; + return false; if (EqualityComparer.Default.Equals((BackgroundScreen)CurrentScreen, screen)) - return; + return false; base.Push(screen); + return true; } } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index e3fe14a585..9aec2a5c19 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -186,17 +186,14 @@ namespace osu.Game.Screens { applyArrivingDefaults(false); - backgroundStack?.Push(ownedBackground = CreateBackground()); - - background = backgroundStack?.CurrentScreen as BackgroundScreen; - - if (background != ownedBackground) + if (backgroundStack?.Push(ownedBackground = CreateBackground()) != true) { - // background may have not been replaced, at which point we don't want to track the background lifetime. + // If the constructed instance was not actually pushed to the background stack, we don't want to track it unnecessarily. ownedBackground?.Dispose(); ownedBackground = null; } + background = backgroundStack?.CurrentScreen as BackgroundScreen; base.OnEntering(last); } From 4658577b1d272aceba1624b0954ffff11e2cb01f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Sep 2021 15:18:59 +0900 Subject: [PATCH 62/75] Factor in total score calculation time in results screen load --- .../Playlists/PlaylistsResultsScreen.cs | 32 ++++++++++++------- osu.Game/Screens/Ranking/ResultsScreen.cs | 14 ++++---- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index 2b252f9db7..89bc659f63 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -32,6 +32,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private ScoreManager scoreManager { get; set; } + public PlaylistsResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, bool allowRetry, bool allowWatchingReplay = true) : base(score, allowRetry, allowWatchingReplay) { @@ -166,23 +169,28 @@ namespace osu.Game.Screens.OnlinePlay.Playlists /// An optional pivot around which the scores were retrieved. private void performSuccessCallback([NotNull] Action> callback, [NotNull] List scores, [CanBeNull] MultiplayerScores pivot = null) { - var scoreInfos = new List(scores.Select(s => s.CreateScoreInfo(playlistItem))); + var scoreInfos = scores.Select(s => s.CreateScoreInfo(playlistItem)).ToArray(); - // Select a score if we don't already have one selected. - // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). - if (SelectedScore.Value == null) + // Score panels calculate total score before displaying, which can take some time. In order to count that calculation as part of the loading spinner display duration, + // calculate the total scores locally before invoking the success callback. + scoreManager.OrderByTotalScoreAsync(scoreInfos).ContinueWith(_ => Schedule(() => { - Schedule(() => + // Select a score if we don't already have one selected. + // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). + if (SelectedScore.Value == null) { - // Prefer selecting the local user's score, or otherwise default to the first visible score. - SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); - }); - } + Schedule(() => + { + // Prefer selecting the local user's score, or otherwise default to the first visible score. + SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); + }); + } - // Invoke callback to add the scores. Exclude the user's current score which was added previously. - callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID)); + // Invoke callback to add the scores. Exclude the user's current score which was added previously. + callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID)); - hideLoadingSpinners(pivot); + hideLoadingSpinners(pivot); + })); } private void hideLoadingSpinners([CanBeNull] MultiplayerScores pivot = null) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index d44d1f2cc9..822ee1cf90 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -52,8 +52,7 @@ namespace osu.Game.Screens.Ranking private Drawable bottomPanel; private Container detachedPanelContainer; - private bool fetchedInitialScores; - private APIRequest nextPageRequest; + private bool lastFetchCompleted; private readonly bool allowRetry; private readonly bool allowWatchingReplay; @@ -191,8 +190,10 @@ namespace osu.Game.Screens.Ranking { base.Update(); - if (fetchedInitialScores && nextPageRequest == null) + if (lastFetchCompleted) { + APIRequest nextPageRequest = null; + if (ScorePanelList.IsScrolledToStart) nextPageRequest = FetchNextPage(-1, fetchScoresCallback); else if (ScorePanelList.IsScrolledToEnd) @@ -200,10 +201,7 @@ namespace osu.Game.Screens.Ranking if (nextPageRequest != null) { - // Scheduled after children to give the list a chance to update its scroll position and not potentially trigger a second request too early. - nextPageRequest.Success += () => ScheduleAfterChildren(() => nextPageRequest = null); - nextPageRequest.Failure += _ => ScheduleAfterChildren(() => nextPageRequest = null); - + lastFetchCompleted = false; api.Queue(nextPageRequest); } } @@ -229,7 +227,7 @@ namespace osu.Game.Screens.Ranking foreach (var s in scores) addScore(s); - fetchedInitialScores = true; + lastFetchCompleted = true; }); public override void OnEntering(IScreen last) From 5b13b566b5151dce86f1f93f54604bbde2f44514 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 15:19:23 +0900 Subject: [PATCH 63/75] Reduce startup overhead during default key binding handling --- .../Database/TestRealmKeyBindingStore.cs | 5 +- osu.Game/Input/RealmKeyBindingStore.cs | 69 ++++++++++--------- osu.Game/OsuGameBase.cs | 5 +- 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 642ecf00b8..8be74f1a7c 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -11,6 +11,7 @@ using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; +using osu.Game.Rulesets; using Realms; namespace osu.Game.Tests.Database @@ -42,7 +43,7 @@ namespace osu.Game.Tests.Database KeyBindingContainer testContainer = new TestKeyBindingContainer(); - keyBindingStore.Register(testContainer); + keyBindingStore.Register(testContainer, Enumerable.Empty()); Assert.That(queryCount(), Is.EqualTo(3)); @@ -66,7 +67,7 @@ namespace osu.Game.Tests.Database { KeyBindingContainer testContainer = new TestKeyBindingContainer(); - keyBindingStore.Register(testContainer); + keyBindingStore.Register(testContainer, Enumerable.Empty()); using (var primaryUsage = realmContextFactory.GetForRead()) { diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 9089169877..03cb4031ca 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -46,52 +46,53 @@ namespace osu.Game.Input } /// - /// Register a new type of , adding default bindings from . + /// Register all defaults for this store. /// /// The container to populate defaults from. - public void Register(KeyBindingContainer container) => insertDefaults(container.DefaultKeyBindings); - - /// - /// Register a ruleset, adding default bindings for each of its variants. - /// - /// The ruleset to populate defaults from. - public void Register(RulesetInfo ruleset) - { - var instance = ruleset.CreateInstance(); - - foreach (var variant in instance.AvailableVariants) - insertDefaults(instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); - } - - private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) + /// The rulesets to populate defaults from. + public void Register(KeyBindingContainer container, IEnumerable rulesets) { using (var usage = realmFactory.GetForWrite()) { - // compare counts in database vs defaults - foreach (var defaultsForAction in defaults.GroupBy(k => k.Action)) + // intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed. + // this is much faster as a result. + var existingBindings = usage.Realm.All().ToList(); + + insertDefaults(usage, existingBindings, container.DefaultKeyBindings); + + foreach (var ruleset in rulesets) { - int existingCount = usage.Realm.All().Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key); - - if (defaultsForAction.Count() <= existingCount) - continue; - - foreach (var k in defaultsForAction.Skip(existingCount)) - { - // insert any defaults which are missing. - usage.Realm.Add(new RealmKeyBinding - { - KeyCombinationString = k.KeyCombination.ToString(), - ActionInt = (int)k.Action, - RulesetID = rulesetId, - Variant = variant - }); - } + var instance = ruleset.CreateInstance(); + foreach (var variant in instance.AvailableVariants) + insertDefaults(usage, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); } usage.Commit(); } } + private void insertDefaults(RealmContextFactory.RealmUsage usage, List existingBindings, IEnumerable defaults, int? rulesetId = null, int? variant = null) + { + // compare counts in database vs defaults for each action type. + foreach (var defaultsForAction in defaults.GroupBy(k => k.Action)) + { + // avoid performing redundant queries when the database is empty and needs to be re-filled. + int existingCount = existingBindings.Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key); + + if (defaultsForAction.Count() <= existingCount) + continue; + + // insert any defaults which are missing. + usage.Realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding + { + KeyCombinationString = k.KeyCombination.ToString(), + ActionInt = (int)k.Action, + RulesetID = rulesetId, + Variant = variant + })); + } + } + /// /// Keys which should not be allowed for gameplay input purposes. /// diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 762216e93c..8563c4e171 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -351,10 +351,7 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); KeyBindingStore = new RealmKeyBindingStore(realmFactory); - KeyBindingStore.Register(globalBindings); - - foreach (var r in RulesetStore.AvailableRulesets) - KeyBindingStore.Register(r); + KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); dependencies.Cache(globalBindings); From 44b1af5ae4419495dcba68f6472b609e43656726 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 15:28:52 +0900 Subject: [PATCH 64/75] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8a9bf1b9cd..05367c00f6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ebe3de6ea4..ae423bac8c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 1714bae53c..be737392e1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 61f819b66d16f0d65a14f767cd66d70d13379f00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Sep 2021 15:51:57 +0900 Subject: [PATCH 65/75] Add COE and better PR condition --- .github/workflows/test-diffcalc.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index d963e49d9f..efa36712be 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -22,12 +22,13 @@ jobs: runs-on: ubuntu-latest if: | - contains(github.event.comment.html_url, '/pull/') && + ${{ github.event.issue.pull_request }} && contains(github.event.comment.body, '!pp check') && ${{ github.event.comment.author_association == 'MEMBER' }} strategy: fail-fast: false + continue-on-error: true matrix: ruleset: - { name: osu, id: 0 } From 842696c388a6b2d1401b4a41fbe1d39af53491d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Sep 2021 15:54:58 +0900 Subject: [PATCH 66/75] Fix incorrect definition --- .github/workflows/test-diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index efa36712be..7728d91152 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -20,6 +20,7 @@ jobs: diffcalc: name: Diffcalc runs-on: ubuntu-latest + continue-on-error: true if: | ${{ github.event.issue.pull_request }} && @@ -28,7 +29,6 @@ jobs: strategy: fail-fast: false - continue-on-error: true matrix: ruleset: - { name: osu, id: 0 } From f3d2d93aa14227f41b6f92e250de9aff5d484164 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 16:09:22 +0900 Subject: [PATCH 67/75] Remove stray newline --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 34a9610804..631455b727 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -201,7 +201,6 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestFetchScoresAfterShowingStatistics() - { DelayedFetchResultsScreen screen = null; From b6c80f04b07b33ec6a3504a6ae968c32d724dc75 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 7 Sep 2021 16:44:45 +0900 Subject: [PATCH 68/75] Add "featured artists" filter to beatmap search --- .../UserInterface/TestSceneBeatmapListingSearchControl.cs | 3 ++- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 3 ++- osu.Game/Overlays/BeatmapListing/SearchGeneral.cs | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index abd1baf0ac..008d91f649 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using Humanizer; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -73,7 +74,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); - control.General.BindCollectionChanged((u, v) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().ToLowerInvariant())) : "")}", true); + control.General.BindCollectionChanged((u, v) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().Underscore())) : "")}", true); control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 8ce495e274..ae082ca82e 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using Humanizer; using JetBrains.Annotations; using osu.Framework.IO.Network; using osu.Game.Extensions; @@ -83,7 +84,7 @@ namespace osu.Game.Online.API.Requests req.AddParameter("q", query); if (General != null && General.Any()) - req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().ToLowerInvariant()))); + req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().Underscore()))); if (ruleset.ID.HasValue) req.AddParameter("m", ruleset.ID.Value.ToString()); diff --git a/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs index d334b82e88..9387020bdf 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs @@ -19,6 +19,10 @@ namespace osu.Game.Overlays.BeatmapListing [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.GeneralFollows))] [Description("Subscribed mappers")] - Follows + Follows, + + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.GeneralFeaturedArtists))] + [Description("Featured artists")] + FeaturedArtists } } From d922210d2feda6f03de5717d3ca53e99b4864feb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 16:46:27 +0900 Subject: [PATCH 69/75] Fix `TestSceneDeleteLocalScore` not properly comparing post-delete scores --- .../TestSceneDeleteLocalScore.cs | 19 +++++++------ .../Online/Leaderboards/LeaderboardScore.cs | 27 ++++++++++--------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 8efee2723f..2e30ed9827 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.UserInterface private BeatmapManager beatmapManager; private ScoreManager scoreManager; - private readonly List scores = new List(); + private readonly List importedScores = new List(); private BeatmapInfo beatmap; [Cached] @@ -100,11 +100,9 @@ namespace osu.Game.Tests.Visual.UserInterface User = new User { Username = "TestUser" }, }; - scores.Add(scoreManager.Import(score).Result); + importedScores.Add(scoreManager.Import(score).Result); } - scores.Sort(Comparer.Create((s1, s2) => s2.TotalScore.CompareTo(s1.TotalScore))); - return dependencies; } @@ -134,9 +132,14 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDeleteViaRightClick() { + ScoreInfo scoreBeingDeleted = null; AddStep("open menu for top score", () => { - InputManager.MoveMouseTo(leaderboard.ChildrenOfType().First()); + var leaderboardScore = leaderboard.ChildrenOfType().First(); + + scoreBeingDeleted = leaderboardScore.Score; + + InputManager.MoveMouseTo(leaderboardScore); InputManager.Click(MouseButton.Right); }); @@ -158,14 +161,14 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s != scores[0])); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID)); } [Test] public void TestDeleteViaDatabase() { - AddStep("delete top score", () => scoreManager.Delete(scores[0])); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s != scores[0])); + AddStep("delete top score", () => scoreManager.Delete(importedScores[0])); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != importedScores[0].OnlineScoreID)); } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 934b905a1a..090a4014aa 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -34,6 +34,8 @@ namespace osu.Game.Online.Leaderboards { public const float HEIGHT = 60; + public readonly ScoreInfo Score; + private const float corner_radius = 5; private const float edge_margin = 5; private const float background_alpha = 0.25f; @@ -41,7 +43,6 @@ namespace osu.Game.Online.Leaderboards protected Container RankContainer { get; private set; } - private readonly ScoreInfo score; private readonly int? rank; private readonly bool allowHighlight; @@ -67,7 +68,7 @@ namespace osu.Game.Online.Leaderboards public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true) { - this.score = score; + this.Score = score; this.rank = rank; this.allowHighlight = allowHighlight; @@ -78,9 +79,9 @@ namespace osu.Game.Online.Leaderboards [BackgroundDependencyLoader] private void load(IAPIProvider api, OsuColour colour, ScoreManager scoreManager) { - var user = score.User; + var user = Score.User; - statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList(); + statisticsLabels = GetStatistics(Score).Select(s => new ScoreComponentLabel(s)).ToList(); ClickableAvatar innerAvatar; @@ -198,7 +199,7 @@ namespace osu.Game.Online.Leaderboards { TextColour = Color4.White, GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Current = scoreManager.GetBindableTotalScoreString(score), + Current = scoreManager.GetBindableTotalScoreString(Score), Font = OsuFont.Numeric.With(size: 23), }, RankContainer = new Container @@ -206,7 +207,7 @@ namespace osu.Game.Online.Leaderboards Size = new Vector2(40f, 20f), Children = new[] { - scoreRank = new UpdateableRank(score.Rank) + scoreRank = new UpdateableRank(Score.Rank) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -223,7 +224,7 @@ namespace osu.Game.Online.Leaderboards AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(1), - ChildrenEnumerable = score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) + ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) }, }, }, @@ -389,14 +390,14 @@ namespace osu.Game.Online.Leaderboards { List items = new List(); - if (score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null) - items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = score.Mods)); + if (Score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null) + items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods)); - if (score.Files?.Count > 0) - items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(score))); + if (Score.Files?.Count > 0) + items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(Score))); - if (score.ID != 0) - items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); + if (Score.ID != 0) + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); return items.ToArray(); } From a42527b8f553f83923647259c53f9ce53bf67392 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 16:46:40 +0900 Subject: [PATCH 70/75] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 05367c00f6..7378450c38 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ae423bac8c..d80dd075ee 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index be737392e1..8ce757974e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 91a48084c7b1e71b83f552db216e11f2d1a04143 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 17:25:03 +0900 Subject: [PATCH 71/75] Update asserts in line with framework changes to `PlaybackPosition` --- osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs index e45b8f7dc5..785f31386d 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs @@ -40,10 +40,10 @@ namespace osu.Game.Tests.NonVisual.Skinning assertPlaybackPosition(0); AddStep("set start time to 1000", () => animationTimeReference.AnimationStartTime.Value = 1000); - assertPlaybackPosition(-1000); + assertPlaybackPosition(0); AddStep("set current time to 500", () => animationTimeReference.ManualClock.CurrentTime = 500); - assertPlaybackPosition(-500); + assertPlaybackPosition(0); } private void assertPlaybackPosition(double expectedPosition) From 5ab2f4b38665b0971fff93d752b7213b176934b4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 7 Sep 2021 17:31:54 +0900 Subject: [PATCH 72/75] Give an orange colour to "featured artists" filter to match web --- .../BeatmapListingSearchControl.cs | 2 +- .../BeatmapSearchGeneralFilterRow.cs | 39 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 4 +- 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 0626f236b8..d1e2ac38df 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -127,7 +127,7 @@ namespace osu.Game.Overlays.BeatmapListing Padding = new MarginPadding { Horizontal = 10 }, Children = new Drawable[] { - generalFilter = new BeatmapSearchMultipleSelectionFilterRow(BeatmapsStrings.ListingSearchFiltersGeneral), + generalFilter = new BeatmapSearchGeneralFilterRow(), modeFilter = new BeatmapSearchRulesetFilterRow(), categoryFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersStatus), genreFilter = new BeatmapSearchFilterRow(BeatmapsStrings.ListingSearchFiltersGenre), diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs new file mode 100644 index 0000000000..fb9e1c0420 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Resources.Localisation.Web; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchGeneralFilterRow : BeatmapSearchMultipleSelectionFilterRow + { + public BeatmapSearchGeneralFilterRow() + : base(BeatmapsStrings.ListingSearchFiltersGeneral) + { + } + + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new GeneralFilter(); + + private class GeneralFilter : MultipleSelectionFilter + { + protected override MultipleSelectionFilterTabItem CreateTabItem(SearchGeneral value) + { + if (value == SearchGeneral.FeaturedArtists) + return new FeaturedArtistsTabItem(); + + return new MultipleSelectionFilterTabItem(value); + } + } + + private class FeaturedArtistsTabItem : MultipleSelectionFilterTabItem + { + public FeaturedArtistsTabItem() + : base(SearchGeneral.FeaturedArtists) + { + } + + protected override Color4 GetStateColour() => OverlayColourProvider.Orange.Colour1; + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 46cb1e822f..9274cf20aa 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -71,10 +71,10 @@ namespace osu.Game.Overlays.BeatmapListing private void updateState() { - text.FadeColour(IsHovered ? colourProvider.Light1 : getStateColour(), 200, Easing.OutQuint); + text.FadeColour(IsHovered ? colourProvider.Light1 : GetStateColour(), 200, Easing.OutQuint); text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular); } - private Color4 getStateColour() => Active.Value ? colourProvider.Content1 : colourProvider.Light2; + protected virtual Color4 GetStateColour() => Active.Value ? colourProvider.Content1 : colourProvider.Light2; } } From 92f59c10f5108d23d84698d0b5c978726c629b5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 17:45:21 +0900 Subject: [PATCH 73/75] Remove redundant `this.` in assignment --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 090a4014aa..26749a23f9 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -68,7 +68,8 @@ namespace osu.Game.Online.Leaderboards public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true) { - this.Score = score; + Score = score; + this.rank = rank; this.allowHighlight = allowHighlight; From 3d8faea4b073718900da16d0a0b522fb700aed9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Sep 2021 18:52:25 +0900 Subject: [PATCH 74/75] Simplify nesting of `OrderByTotalScoreAsync` --- osu.Game/Scoring/ScoreManager.cs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 2cda8959f4..81e701f001 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -116,25 +116,20 @@ namespace osu.Game.Scoring { var difficultyCache = difficulties?.Invoke(); - if (difficultyCache == null) - return orderByTotalScore(scores); - - // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. - foreach (var s in scores) + if (difficultyCache != null) { - await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); + // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. + foreach (var s in scores) + { + await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + } } - return orderByTotalScore(scores); - - ScoreInfo[] orderByTotalScore(IEnumerable incoming) - { - // We're calling .Result, but this should not be a blocking call due to the above GetDifficultyAsync() calls. - return incoming.OrderByDescending(s => GetTotalScoreAsync(s, cancellationToken: cancellationToken).Result) - .ThenBy(s => s.OnlineScoreID) - .ToArray(); - } + // We're calling .Result, but this should not be a blocking call due to the above GetDifficultyAsync() calls. + return scores.OrderByDescending(s => GetTotalScoreAsync(s, cancellationToken: cancellationToken).Result) + .ThenBy(s => s.OnlineScoreID) + .ToArray(); } /// From 2f42a8461e64ac7f33b75fcb2171b61bc296c99c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 7 Sep 2021 20:19:07 +0900 Subject: [PATCH 75/75] Fix diffcalc workflow triggering too often --- .github/workflows/test-diffcalc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-diffcalc.yml b/.github/workflows/test-diffcalc.yml index 7728d91152..4274d01bab 100644 --- a/.github/workflows/test-diffcalc.yml +++ b/.github/workflows/test-diffcalc.yml @@ -23,9 +23,9 @@ jobs: continue-on-error: true if: | - ${{ github.event.issue.pull_request }} && + github.event.issue.pull_request && contains(github.event.comment.body, '!pp check') && - ${{ github.event.comment.author_association == 'MEMBER' }} + (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') strategy: fail-fast: false