From f0caa1006631c08c9f15e665e865b3108543a64c Mon Sep 17 00:00:00 2001 From: Tollii Date: Fri, 5 Nov 2021 23:53:48 +0100 Subject: [PATCH 01/21] Add support for a provided cancellation token for GetPlayableBeatmap() --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 4 +++- osu.Game/Beatmaps/WorkingBeatmap.cs | 7 ++++--- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index a916b37b85..6f0f408060 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets; @@ -57,9 +58,10 @@ namespace osu.Game.Beatmaps /// The to create a playable for. /// The s to apply to the . /// The maximum length in milliseconds to wait for load to complete. Defaults to 10,000ms. + /// Externally provided cancellation token. /// The converted . /// If could not be converted to . - IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null); + IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default); /// /// Load a new audio track instance for this beatmap. This should be called once before accessing . diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index d2c0f7de0f..affc19c700 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null) + public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { using (var cancellationSource = createCancellationTokenSource(timeout)) { @@ -105,7 +105,7 @@ namespace osu.Game.Beatmaps } // Convert - IBeatmap converted = converter.Convert(cancellationSource.Token); + IBeatmap converted = converter.Convert(cancellationToken != CancellationToken.None ? cancellationToken : cancellationSource.Token); // Apply conversion mods to the result foreach (var mod in mods.OfType()) @@ -143,7 +143,7 @@ namespace osu.Game.Beatmaps if (cancellationSource.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); - obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, cancellationSource.Token); + obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, cancellationToken != CancellationToken.None ? cancellationToken : cancellationSource.Token); } } catch (OperationCanceledException) @@ -167,6 +167,7 @@ namespace osu.Game.Beatmaps foreach (var mod in mods.OfType()) { cancellationSource.Token.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); mod.ApplyToBeatmap(converted); } diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index ef289c2a20..922d1e6bbe 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -210,7 +210,7 @@ namespace osu.Game.Screens.Play.HUD this.gameplayBeatmap = gameplayBeatmap; } - public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null) + public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) => gameplayBeatmap; protected override IBeatmap GetBeatmap() => gameplayBeatmap; From eb7d04bc77da85ea95461828e0988bd1668d2e58 Mon Sep 17 00:00:00 2001 From: Tollii Date: Sat, 6 Nov 2021 00:19:48 +0100 Subject: [PATCH 02/21] Add cancellation token support for beatmap difficulty calculation. --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 9 +++++---- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- .../Difficulty/DifficultyCalculator.cs | 18 +++++++++++------- osu.Game/Stores/BeatmapImporter.cs | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 035f438b89..bbe5ecb0df 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -147,13 +147,13 @@ namespace osu.Game.Beatmaps if (CheckExists(lookup, out var existing)) return existing; - return computeDifficulty(lookup); + return computeDifficulty(lookup, token); }, token, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } public Task> GetTimedDifficultyAttributesAsync(WorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken token = default) { - return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods), + return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods, token), token, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); @@ -270,8 +270,9 @@ namespace osu.Game.Beatmaps /// Computes the difficulty defined by a key, and stores it to the timed cache. /// /// The that defines the computation parameters. + /// The cancellation token. /// The . - private StarDifficulty computeDifficulty(in DifficultyCacheLookup key) + private StarDifficulty computeDifficulty(in DifficultyCacheLookup key, CancellationToken cancellationToken = default) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. var beatmapInfo = key.BeatmapInfo; @@ -283,7 +284,7 @@ namespace osu.Game.Beatmaps Debug.Assert(ruleset != null); var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo)); - var attributes = calculator.Calculate(key.OrderedMods); + var attributes = calculator.Calculate(key.OrderedMods, cancellationToken); return new StarDifficulty(attributes); } diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index f148d05aca..2166ae1dfc 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -408,7 +408,7 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Ruleset = ruleset; // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; + beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate(null).StarRating ?? 0; beatmap.BeatmapInfo.Length = calculateLength(beatmap); beatmap.BeatmapInfo.BPM = 60000 / beatmap.GetMostCommonBeatLength(); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 5b4284dc2f..4fc984b491 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; @@ -39,10 +40,11 @@ namespace osu.Game.Rulesets.Difficulty /// Calculates the difficulty of the beatmap using a specific mod combination. /// /// The mods that should be applied to the beatmap. + /// The cancellation token. /// A structure describing the difficulty of the beatmap. - public DifficultyAttributes Calculate(params Mod[] mods) + public DifficultyAttributes Calculate(IEnumerable mods, CancellationToken cancellationToken = default) { - preProcess(mods); + preProcess(mods, cancellationToken); var skills = CreateSkills(Beatmap, playableMods, clockRate); @@ -62,10 +64,11 @@ namespace osu.Game.Rulesets.Difficulty /// Calculates the difficulty of the beatmap and returns a set of representing the difficulty at every relevant time value in the beatmap. /// /// The mods that should be applied to the beatmap. + /// The cancellation token. /// The set of . - public List CalculateTimed(params Mod[] mods) + public List CalculateTimed(IEnumerable mods, CancellationToken cancellationToken = default) { - preProcess(mods); + preProcess(mods, cancellationToken); var attribs = new List(); @@ -99,7 +102,7 @@ namespace osu.Game.Rulesets.Difficulty if (combination is MultiMod multi) yield return Calculate(multi.Mods); else - yield return Calculate(combination); + yield return Calculate(new[] { combination }); } } @@ -112,11 +115,12 @@ namespace osu.Game.Rulesets.Difficulty /// Performs required tasks before every calculation. /// /// The original list of s. - private void preProcess(Mod[] mods) + /// The cancellation cancellationToken. + private void preProcess(IEnumerable mods, CancellationToken cancellationToken = default) { playableMods = mods.Select(m => m.DeepClone()).ToArray(); - Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, playableMods); + Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, playableMods, cancellationToken: cancellationToken); var track = new TrackVirtual(10000); playableMods.OfType().ForEach(m => m.ApplyToTrack(track)); diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 787b1ddd60..7916583ce1 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -284,7 +284,7 @@ namespace osu.Game.Stores decoded.BeatmapInfo.Ruleset = rulesetInstance.RulesetInfo; // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.StarRating = rulesetInstance.CreateDifficultyCalculator(new DummyConversionBeatmap(decoded)).Calculate().StarRating; + beatmap.StarRating = rulesetInstance.CreateDifficultyCalculator(new DummyConversionBeatmap(decoded)).Calculate(null).StarRating; beatmap.Length = calculateLength(decoded); beatmap.BPM = 60000 / decoded.GetMostCommonBeatLength(); } From cf0b757b16f8369efd82968b6357aafe120e8549 Mon Sep 17 00:00:00 2001 From: Tollii Date: Sat, 6 Nov 2021 16:03:53 +0100 Subject: [PATCH 03/21] Fix PR comments. Nitpick, more cancellation token checks. --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 +- osu.Game/Beatmaps/IWorkingBeatmap.cs | 4 ++-- osu.Game/Beatmaps/WorkingBeatmap.cs | 24 +++++++++---------- .../Difficulty/DifficultyCalculator.cs | 18 ++++++++++---- .../Play/HUD/PerformancePointsCounter.cs | 2 +- osu.Game/Stores/BeatmapImporter.cs | 2 +- 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 2166ae1dfc..f148d05aca 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -408,7 +408,7 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Ruleset = ruleset; // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate(null).StarRating ?? 0; + beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; beatmap.BeatmapInfo.Length = calculateLength(beatmap); beatmap.BeatmapInfo.BPM = 60000 / beatmap.GetMostCommonBeatLength(); diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 6f0f408060..3bbfb2a889 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -58,10 +58,10 @@ namespace osu.Game.Beatmaps /// The to create a playable for. /// The s to apply to the . /// The maximum length in milliseconds to wait for load to complete. Defaults to 10,000ms. - /// Externally provided cancellation token. + /// Cancellation token that cancels the beatmap loading process. /// The converted . /// If could not be converted to . - IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default); + IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken timeoutToken = default); /// /// Load a new audio track instance for this beatmap. This should be called once before accessing . diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index affc19c700..e0c2ae7873 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -81,9 +81,10 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) + public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken timeoutToken = default) { - using (var cancellationSource = createCancellationTokenSource(timeout)) + using (var timeoutSource = createTimeoutTokenSource(timeout)) + using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken, timeoutSource.Token)) { mods ??= Array.Empty(); @@ -98,19 +99,19 @@ namespace osu.Game.Beatmaps // Apply conversion mods foreach (var mod in mods.OfType()) { - if (cancellationSource.IsCancellationRequested) + if (linkedTokenSource.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToBeatmapConverter(converter); } // Convert - IBeatmap converted = converter.Convert(cancellationToken != CancellationToken.None ? cancellationToken : cancellationSource.Token); + IBeatmap converted = converter.Convert(linkedTokenSource.Token); // Apply conversion mods to the result foreach (var mod in mods.OfType()) { - if (cancellationSource.IsCancellationRequested) + if (linkedTokenSource.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToBeatmap(converted); @@ -121,7 +122,7 @@ namespace osu.Game.Beatmaps { foreach (var mod in mods.OfType()) { - if (cancellationSource.IsCancellationRequested) + if (linkedTokenSource.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToDifficulty(converted.Difficulty); @@ -140,10 +141,10 @@ namespace osu.Game.Beatmaps { foreach (var obj in converted.HitObjects) { - if (cancellationSource.IsCancellationRequested) + if (linkedTokenSource.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); - obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, cancellationToken != CancellationToken.None ? cancellationToken : cancellationSource.Token); + obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, linkedTokenSource.Token); } } catch (OperationCanceledException) @@ -155,7 +156,7 @@ namespace osu.Game.Beatmaps { foreach (var obj in converted.HitObjects) { - if (cancellationSource.IsCancellationRequested) + if (linkedTokenSource.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToHitObject(obj); @@ -166,8 +167,7 @@ namespace osu.Game.Beatmaps foreach (var mod in mods.OfType()) { - cancellationSource.Token.ThrowIfCancellationRequested(); - cancellationToken.ThrowIfCancellationRequested(); + linkedTokenSource.Token.ThrowIfCancellationRequested(); mod.ApplyToBeatmap(converted); } @@ -200,7 +200,7 @@ namespace osu.Game.Beatmaps } } - private CancellationTokenSource createCancellationTokenSource(TimeSpan? timeout) + private CancellationTokenSource createTimeoutTokenSource(TimeSpan? timeout) { if (Debugger.IsAttached) // ignore timeout when debugger is attached (may be breakpointing / debugging). diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 4fc984b491..ffad6131be 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -42,8 +42,9 @@ namespace osu.Game.Rulesets.Difficulty /// The mods that should be applied to the beatmap. /// The cancellation token. /// A structure describing the difficulty of the beatmap. - public DifficultyAttributes Calculate(IEnumerable mods, CancellationToken cancellationToken = default) + public DifficultyAttributes Calculate(IEnumerable mods = null, CancellationToken cancellationToken = default) { + cancellationToken.ThrowIfCancellationRequested(); preProcess(mods, cancellationToken); var skills = CreateSkills(Beatmap, playableMods, clockRate); @@ -54,7 +55,10 @@ namespace osu.Game.Rulesets.Difficulty foreach (var hitObject in getDifficultyHitObjects()) { foreach (var skill in skills) + { + cancellationToken.ThrowIfCancellationRequested(); skill.ProcessInternal(hitObject); + } } return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate); @@ -68,6 +72,7 @@ namespace osu.Game.Rulesets.Difficulty /// The set of . public List CalculateTimed(IEnumerable mods, CancellationToken cancellationToken = default) { + cancellationToken.ThrowIfCancellationRequested(); preProcess(mods, cancellationToken); var attribs = new List(); @@ -83,7 +88,10 @@ namespace osu.Game.Rulesets.Difficulty progressiveBeatmap.HitObjects.Add(hitObject.BaseObject); foreach (var skill in skills) + { + cancellationToken.ThrowIfCancellationRequested(); skill.ProcessInternal(hitObject); + } attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime * clockRate, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate))); } @@ -102,7 +110,7 @@ namespace osu.Game.Rulesets.Difficulty if (combination is MultiMod multi) yield return Calculate(multi.Mods); else - yield return Calculate(new[] { combination }); + yield return Calculate(combination.Yield()); } } @@ -115,12 +123,12 @@ namespace osu.Game.Rulesets.Difficulty /// Performs required tasks before every calculation. /// /// The original list of s. - /// The cancellation cancellationToken. - private void preProcess(IEnumerable mods, CancellationToken cancellationToken = default) + /// The cancellation timeoutToken. + private void preProcess(IEnumerable mods = null, CancellationToken cancellationToken = default) { playableMods = mods.Select(m => m.DeepClone()).ToArray(); - Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, playableMods, cancellationToken: cancellationToken); + Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, playableMods, timeoutToken: cancellationToken); var track = new TrackVirtual(10000); playableMods.OfType().ForEach(m => m.ApplyToTrack(track)); diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 922d1e6bbe..bd06e71304 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -210,7 +210,7 @@ namespace osu.Game.Screens.Play.HUD this.gameplayBeatmap = gameplayBeatmap; } - public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) + public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken timeoutToken = default) => gameplayBeatmap; protected override IBeatmap GetBeatmap() => gameplayBeatmap; diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 7916583ce1..787b1ddd60 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -284,7 +284,7 @@ namespace osu.Game.Stores decoded.BeatmapInfo.Ruleset = rulesetInstance.RulesetInfo; // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.StarRating = rulesetInstance.CreateDifficultyCalculator(new DummyConversionBeatmap(decoded)).Calculate(null).StarRating; + beatmap.StarRating = rulesetInstance.CreateDifficultyCalculator(new DummyConversionBeatmap(decoded)).Calculate().StarRating; beatmap.Length = calculateLength(decoded); beatmap.BPM = 60000 / decoded.GetMostCommonBeatLength(); } From d5f5d74a89e880d3510867bab46698455504f7cd Mon Sep 17 00:00:00 2001 From: Tollii Date: Sun, 7 Nov 2021 13:38:00 +0100 Subject: [PATCH 04/21] Rename CancellationToken variable --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 4 ++-- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 ++-- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 3bbfb2a889..1e46e265ac 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -58,10 +58,10 @@ namespace osu.Game.Beatmaps /// The to create a playable for. /// The s to apply to the . /// The maximum length in milliseconds to wait for load to complete. Defaults to 10,000ms. - /// Cancellation token that cancels the beatmap loading process. + /// Cancellation token that cancels the beatmap loading process. /// The converted . /// If could not be converted to . - IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken timeoutToken = default); + IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken token = default); /// /// Load a new audio track instance for this beatmap. This should be called once before accessing . diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index e0c2ae7873..a93c50ce74 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -81,10 +81,10 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken timeoutToken = default) + public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken token = default) { using (var timeoutSource = createTimeoutTokenSource(timeout)) - using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken, timeoutSource.Token)) + using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutSource.Token)) { mods ??= Array.Empty(); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index ffad6131be..6630530b65 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -123,12 +123,12 @@ namespace osu.Game.Rulesets.Difficulty /// Performs required tasks before every calculation. /// /// The original list of s. - /// The cancellation timeoutToken. + /// The cancellation token. private void preProcess(IEnumerable mods = null, CancellationToken cancellationToken = default) { playableMods = mods.Select(m => m.DeepClone()).ToArray(); - Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, playableMods, timeoutToken: cancellationToken); + Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, playableMods, token: cancellationToken); var track = new TrackVirtual(10000); playableMods.OfType().ForEach(m => m.ApplyToTrack(track)); From 5b5e3dc4a2ee03256aac220341ad419d20df9e87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 14:33:15 +0900 Subject: [PATCH 05/21] Revert incorrect mod nullable parameter specification --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 6630530b65..a6f3a75302 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// The original list of s. /// The cancellation token. - private void preProcess(IEnumerable mods = null, CancellationToken cancellationToken = default) + private void preProcess(IEnumerable mods, CancellationToken cancellationToken = default) { playableMods = mods.Select(m => m.DeepClone()).ToArray(); From 97345ac9e6123c8fc3e990bc5fdc244c7ba14206 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 14:33:32 +0900 Subject: [PATCH 06/21] Remove unnecessary `TimeSpan timeout` parameter (`CancellationToken` can now be used) --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 4 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 170 +++++++++--------- .../Play/HUD/PerformancePointsCounter.cs | 2 +- 3 files changed, 85 insertions(+), 91 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 1e46e265ac..ed100d1876 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.IO; using System.Threading; @@ -57,11 +56,10 @@ namespace osu.Game.Beatmaps /// /// The to create a playable for. /// The s to apply to the . - /// The maximum length in milliseconds to wait for load to complete. Defaults to 10,000ms. /// Cancellation token that cancels the beatmap loading process. /// The converted . /// If could not be converted to . - IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken token = default); + IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken token = default); /// /// Load a new audio track instance for this beatmap. This should be called once before accessing . diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index a93c50ce74..0527acca8f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -81,98 +81,94 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken token = default) + public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken token = default) { - using (var timeoutSource = createTimeoutTokenSource(timeout)) - using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutSource.Token)) + mods ??= Array.Empty(); + + var rulesetInstance = ruleset.CreateInstance(); + + IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); + + // Check if the beatmap can be converted + if (Beatmap.HitObjects.Count > 0 && !converter.CanConvert()) + throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter})."); + + // Apply conversion mods + foreach (var mod in mods.OfType()) { - mods ??= Array.Empty(); - - var rulesetInstance = ruleset.CreateInstance(); - - IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); - - // Check if the beatmap can be converted - if (Beatmap.HitObjects.Count > 0 && !converter.CanConvert()) - throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter})."); - - // Apply conversion mods - foreach (var mod in mods.OfType()) - { - if (linkedTokenSource.IsCancellationRequested) - throw new BeatmapLoadTimeoutException(BeatmapInfo); - - mod.ApplyToBeatmapConverter(converter); - } - - // Convert - IBeatmap converted = converter.Convert(linkedTokenSource.Token); - - // Apply conversion mods to the result - foreach (var mod in mods.OfType()) - { - if (linkedTokenSource.IsCancellationRequested) - throw new BeatmapLoadTimeoutException(BeatmapInfo); - - mod.ApplyToBeatmap(converted); - } - - // Apply difficulty mods - if (mods.Any(m => m is IApplicableToDifficulty)) - { - foreach (var mod in mods.OfType()) - { - if (linkedTokenSource.IsCancellationRequested) - throw new BeatmapLoadTimeoutException(BeatmapInfo); - - mod.ApplyToDifficulty(converted.Difficulty); - } - } - - IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted); - - foreach (var mod in mods.OfType()) - mod.ApplyToBeatmapProcessor(processor); - - processor?.PreProcess(); - - // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed - try - { - foreach (var obj in converted.HitObjects) - { - if (linkedTokenSource.IsCancellationRequested) - throw new BeatmapLoadTimeoutException(BeatmapInfo); - - obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, linkedTokenSource.Token); - } - } - catch (OperationCanceledException) - { + if (token.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); - } - foreach (var mod in mods.OfType()) - { - foreach (var obj in converted.HitObjects) - { - if (linkedTokenSource.IsCancellationRequested) - throw new BeatmapLoadTimeoutException(BeatmapInfo); - - mod.ApplyToHitObject(obj); - } - } - - processor?.PostProcess(); - - foreach (var mod in mods.OfType()) - { - linkedTokenSource.Token.ThrowIfCancellationRequested(); - mod.ApplyToBeatmap(converted); - } - - return converted; + mod.ApplyToBeatmapConverter(converter); } + + // Convert + IBeatmap converted = converter.Convert(token); + + // Apply conversion mods to the result + foreach (var mod in mods.OfType()) + { + if (token.IsCancellationRequested) + throw new BeatmapLoadTimeoutException(BeatmapInfo); + + mod.ApplyToBeatmap(converted); + } + + // Apply difficulty mods + if (mods.Any(m => m is IApplicableToDifficulty)) + { + foreach (var mod in mods.OfType()) + { + if (token.IsCancellationRequested) + throw new BeatmapLoadTimeoutException(BeatmapInfo); + + mod.ApplyToDifficulty(converted.Difficulty); + } + } + + IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted); + + foreach (var mod in mods.OfType()) + mod.ApplyToBeatmapProcessor(processor); + + processor?.PreProcess(); + + // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed + try + { + foreach (var obj in converted.HitObjects) + { + if (token.IsCancellationRequested) + throw new BeatmapLoadTimeoutException(BeatmapInfo); + + obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, token); + } + } + catch (OperationCanceledException) + { + throw new BeatmapLoadTimeoutException(BeatmapInfo); + } + + foreach (var mod in mods.OfType()) + { + foreach (var obj in converted.HitObjects) + { + if (token.IsCancellationRequested) + throw new BeatmapLoadTimeoutException(BeatmapInfo); + + mod.ApplyToHitObject(obj); + } + } + + processor?.PostProcess(); + + foreach (var mod in mods.OfType()) + { + token.ThrowIfCancellationRequested(); + mod.ApplyToBeatmap(converted); + } + + return converted; } private CancellationTokenSource loadCancellation = new CancellationTokenSource(); diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index bd06e71304..dea1964faa 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -210,7 +210,7 @@ namespace osu.Game.Screens.Play.HUD this.gameplayBeatmap = gameplayBeatmap; } - public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null, CancellationToken timeoutToken = default) + public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken timeoutToken = default) => gameplayBeatmap; protected override IBeatmap GetBeatmap() => gameplayBeatmap; From c58f21a11531860719268823ba02c2436668ccde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Nov 2021 14:43:46 +0900 Subject: [PATCH 07/21] Handle mods with overloaded method signature instead --- .../Difficulty/DifficultyCalculator.cs | 25 ++++++++++++++++--- .../Difficulty/TimedDifficultyAttributes.cs | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index a6f3a75302..6748158d8f 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using JetBrains.Annotations; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; @@ -36,13 +37,21 @@ namespace osu.Game.Rulesets.Difficulty this.beatmap = beatmap; } + /// + /// Calculates the difficulty of the beatmap with no mods applied. + /// + /// The cancellation token. + /// A structure describing the difficulty of the beatmap. + public DifficultyAttributes Calculate(CancellationToken cancellationToken = default) + => Calculate(Array.Empty(), cancellationToken); + /// /// Calculates the difficulty of the beatmap using a specific mod combination. /// /// The mods that should be applied to the beatmap. /// The cancellation token. /// A structure describing the difficulty of the beatmap. - public DifficultyAttributes Calculate(IEnumerable mods = null, CancellationToken cancellationToken = default) + public DifficultyAttributes Calculate([NotNull] IEnumerable mods, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); preProcess(mods, cancellationToken); @@ -65,12 +74,20 @@ namespace osu.Game.Rulesets.Difficulty } /// - /// Calculates the difficulty of the beatmap and returns a set of representing the difficulty at every relevant time value in the beatmap. + /// Calculates the difficulty of the beatmap with no mods applied and returns a set of representing the difficulty at every relevant time value in the beatmap. + /// + /// The cancellation token. + /// The set of . + public List CalculateTimed(CancellationToken cancellationToken = default) + => CalculateTimed(Array.Empty(), cancellationToken); + + /// + /// Calculates the difficulty of the beatmap using a specific mod combination and returns a set of representing the difficulty at every relevant time value in the beatmap. /// /// The mods that should be applied to the beatmap. /// The cancellation token. /// The set of . - public List CalculateTimed(IEnumerable mods, CancellationToken cancellationToken = default) + public List CalculateTimed([NotNull] IEnumerable mods, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); preProcess(mods, cancellationToken); @@ -124,7 +141,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// The original list of s. /// The cancellation token. - private void preProcess(IEnumerable mods, CancellationToken cancellationToken = default) + private void preProcess([NotNull] IEnumerable mods, CancellationToken cancellationToken = default) { playableMods = mods.Select(m => m.DeepClone()).ToArray(); diff --git a/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs index 2509971389..c07d1cd46e 100644 --- a/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Difficulty { /// /// Wraps a object and adds a time value for which the attribute is valid. - /// Output by . + /// Output by DifficultyCalculator.CalculateTimed methods. /// public class TimedDifficultyAttributes : IComparable { From 6cca657a2d48c6424759493d8fe264791ed211db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Nov 2021 14:45:51 +0900 Subject: [PATCH 08/21] Standardise naming of `CancellationToken` parameters --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 12 ++++++------ osu.Game/Beatmaps/IWorkingBeatmap.cs | 4 ++-- osu.Game/Beatmaps/WorkingBeatmap.cs | 18 +++++++++--------- .../Difficulty/DifficultyCalculator.cs | 2 +- .../Play/HUD/PerformancePointsCounter.cs | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index dabff0141e..d761ea3212 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -140,21 +140,21 @@ namespace osu.Game.Beatmaps return GetAsync(new DifficultyCacheLookup(localBeatmapInfo, localRulesetInfo, mods), cancellationToken); } - protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default) + protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken cancellationToken = default) { return Task.Factory.StartNew(() => { if (CheckExists(lookup, out var existing)) return existing; - return computeDifficulty(lookup, token); - }, token, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); + return computeDifficulty(lookup, cancellationToken); + }, cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } - public Task> GetTimedDifficultyAttributesAsync(WorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken token = default) + public Task> GetTimedDifficultyAttributesAsync(WorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken cancellationToken = default) { - return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods, token), - token, + return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods, cancellationToken), + cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 39f6ad7b58..5e9b77392a 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -92,10 +92,10 @@ namespace osu.Game.Beatmaps /// /// The to create a playable for. /// The s to apply to the . - /// Cancellation token that cancels the beatmap loading process. + /// Cancellation token that cancels the beatmap loading process. /// The converted . /// If could not be converted to . - IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken token = default); + IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken cancellationToken = default); /// /// Load a new audio track instance for this beatmap. This should be called once before accessing . diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index b4db4b9f9f..4098a57bf2 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -79,7 +79,7 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken token = default) + public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken cancellationToken = default) { mods ??= Array.Empty(); @@ -97,19 +97,19 @@ namespace osu.Game.Beatmaps // Apply conversion mods foreach (var mod in mods.OfType()) { - if (token.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToBeatmapConverter(converter); } // Convert - IBeatmap converted = converter.Convert(token); + IBeatmap converted = converter.Convert(cancellationToken); // Apply conversion mods to the result foreach (var mod in mods.OfType()) { - if (token.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToBeatmap(converted); @@ -120,7 +120,7 @@ namespace osu.Game.Beatmaps { foreach (var mod in mods.OfType()) { - if (token.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToDifficulty(converted.Difficulty); @@ -139,10 +139,10 @@ namespace osu.Game.Beatmaps { foreach (var obj in converted.HitObjects) { - if (token.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); - obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, token); + obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, cancellationToken); } } catch (OperationCanceledException) @@ -154,7 +154,7 @@ namespace osu.Game.Beatmaps { foreach (var obj in converted.HitObjects) { - if (token.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToHitObject(obj); @@ -165,7 +165,7 @@ namespace osu.Game.Beatmaps foreach (var mod in mods.OfType()) { - token.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); mod.ApplyToBeatmap(converted); } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 6748158d8f..f5b6883b27 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Difficulty { playableMods = mods.Select(m => m.DeepClone()).ToArray(); - Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, playableMods, token: cancellationToken); + Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, playableMods, cancellationToken: cancellationToken); var track = new TrackVirtual(10000); playableMods.OfType().ForEach(m => m.ApplyToTrack(track)); diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 24c6c87c7d..6e679279a4 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -216,7 +216,7 @@ namespace osu.Game.Screens.Play.HUD this.gameplayBeatmap = gameplayBeatmap; } - public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken timeoutToken = default) + public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken cancellationToken = default) => gameplayBeatmap; protected override IBeatmap GetBeatmap() => gameplayBeatmap; From d2a767049437b30e6bdf1eb09e301e2bd92474ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Nov 2021 14:48:02 +0900 Subject: [PATCH 09/21] Remove no longer used helper method --- osu.Game/Beatmaps/WorkingBeatmap.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 4098a57bf2..9be239e3b0 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -188,15 +187,6 @@ namespace osu.Game.Beatmaps } } - private CancellationTokenSource createTimeoutTokenSource(TimeSpan? timeout) - { - if (Debugger.IsAttached) - // ignore timeout when debugger is attached (may be breakpointing / debugging). - return new CancellationTokenSource(); - - return new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(10)); - } - private readonly object beatmapFetchLock = new object(); private Task loadBeatmapAsync() From 13f3e2eea9d3c590cd910e556feab0ee5e9742a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Nov 2021 10:48:33 +0900 Subject: [PATCH 10/21] Add back default timeout to `GetPlayableBeatmap` --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 4 ++-- osu.Game/Beatmaps/WorkingBeatmap.cs | 19 ++++++++++--------- .../Play/HUD/PerformancePointsCounter.cs | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 5e9b77392a..6bf338db59 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -92,10 +92,10 @@ namespace osu.Game.Beatmaps /// /// The to create a playable for. /// The s to apply to the . - /// Cancellation token that cancels the beatmap loading process. + /// Cancellation token that cancels the beatmap loading process. If not provided, a default timeout of 10,000ms will be applied to the load process. /// The converted . /// If could not be converted to . - IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken cancellationToken = default); + IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken? cancellationToken = null); /// /// Load a new audio track instance for this beatmap. This should be called once before accessing . diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 9be239e3b0..a04345ec5c 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -78,8 +78,9 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken cancellationToken = default) + public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken? cancellationToken = null) { + var token = cancellationToken ?? new CancellationTokenSource(10000).Token; mods ??= Array.Empty(); var rulesetInstance = ruleset.CreateInstance(); @@ -96,19 +97,19 @@ namespace osu.Game.Beatmaps // Apply conversion mods foreach (var mod in mods.OfType()) { - if (cancellationToken.IsCancellationRequested) + if (token.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToBeatmapConverter(converter); } // Convert - IBeatmap converted = converter.Convert(cancellationToken); + IBeatmap converted = converter.Convert(token); // Apply conversion mods to the result foreach (var mod in mods.OfType()) { - if (cancellationToken.IsCancellationRequested) + if (token.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToBeatmap(converted); @@ -119,7 +120,7 @@ namespace osu.Game.Beatmaps { foreach (var mod in mods.OfType()) { - if (cancellationToken.IsCancellationRequested) + if (token.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToDifficulty(converted.Difficulty); @@ -138,10 +139,10 @@ namespace osu.Game.Beatmaps { foreach (var obj in converted.HitObjects) { - if (cancellationToken.IsCancellationRequested) + if (token.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); - obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, cancellationToken); + obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, token); } } catch (OperationCanceledException) @@ -153,7 +154,7 @@ namespace osu.Game.Beatmaps { foreach (var obj in converted.HitObjects) { - if (cancellationToken.IsCancellationRequested) + if (token.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); mod.ApplyToHitObject(obj); @@ -164,7 +165,7 @@ namespace osu.Game.Beatmaps foreach (var mod in mods.OfType()) { - cancellationToken.ThrowIfCancellationRequested(); + token.ThrowIfCancellationRequested(); mod.ApplyToBeatmap(converted); } diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 6e679279a4..2f46682b1a 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -216,7 +216,7 @@ namespace osu.Game.Screens.Play.HUD this.gameplayBeatmap = gameplayBeatmap; } - public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken cancellationToken = default) + public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken? cancellationToken = null) => gameplayBeatmap; protected override IBeatmap GetBeatmap() => gameplayBeatmap; From 4d11794d31d0822e0738f49d5adb09492843a021 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Nov 2021 11:06:43 +0900 Subject: [PATCH 11/21] Add test coverage of `GetPlayableBeatmap` timeout and cancellation --- osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs new file mode 100644 index 0000000000..02e4a87a7a --- /dev/null +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs @@ -0,0 +1,102 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Beatmaps; + +namespace osu.Game.Tests.Beatmaps +{ + [TestFixture] + public class WorkingBeatmapTest + { + [Test] + public void TestGetPlayableSuccess() + { + var working = new TestNeverLoadsWorkingBeatmap(); + + working.ResetEvent.Set(); + + Assert.NotNull(working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); + } + + [Test] + public void TestGetPlayableCancellationToken() + { + var working = new TestNeverLoadsWorkingBeatmap(); + + var cts = new CancellationTokenSource(); + var loadStarted = new ManualResetEventSlim(); + var loadCompleted = new ManualResetEventSlim(); + + Task.Factory.StartNew(() => + { + loadStarted.Set(); + Assert.Throws(() => working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, cancellationToken: cts.Token)); + loadCompleted.Set(); + }, TaskCreationOptions.LongRunning); + + Assert.IsTrue(loadStarted.Wait(10000)); + + cts.Cancel(); + + Assert.IsTrue(loadCompleted.Wait(10000)); + + working.ResetEvent.Set(); + } + + [Test] + public void TestGetPlayableDefaultTimeout() + { + var working = new TestNeverLoadsWorkingBeatmap(); + + Assert.Throws(() => working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); + + working.ResetEvent.Set(); + } + + public class TestNeverLoadsWorkingBeatmap : TestWorkingBeatmap + { + public ManualResetEventSlim ResetEvent = new ManualResetEventSlim(); + + public TestNeverLoadsWorkingBeatmap() + : base(new Beatmap()) + { + } + + protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => new TestConverter(beatmap, ResetEvent); + + public class TestConverter : IBeatmapConverter + { + private readonly ManualResetEventSlim resetEvent; + + public TestConverter(IBeatmap beatmap, ManualResetEventSlim resetEvent) + { + this.resetEvent = resetEvent; + Beatmap = beatmap; + } + + public event Action> ObjectConverted; + + protected virtual void OnObjectConverted(HitObject arg1, IEnumerable arg2) => ObjectConverted?.Invoke(arg1, arg2); + + public IBeatmap Beatmap { get; } + + public bool CanConvert() => true; + + public IBeatmap Convert(CancellationToken cancellationToken = default) + { + resetEvent.Wait(cancellationToken); + return new OsuBeatmap(); + } + } + } + } +} From 8528cab40b862c4b03c27c8543ebb9c95f6ea15d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Nov 2021 22:07:24 +0100 Subject: [PATCH 12/21] Add test coverage for ruleset load failure scenario --- osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs index 02e4a87a7a..6ad63a5fbb 100644 --- a/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Moq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -62,6 +63,17 @@ namespace osu.Game.Tests.Beatmaps working.ResetEvent.Set(); } + [Test] + public void TestGetPlayableRulesetLoadFailure() + { + var working = new TestWorkingBeatmap(new Beatmap()); + + // by default mocks return nulls if not set up, which is actually desired here to simulate a ruleset load failure scenario. + var ruleset = new Mock(); + + Assert.Throws(() => working.GetPlayableBeatmap(ruleset.Object)); + } + public class TestNeverLoadsWorkingBeatmap : TestWorkingBeatmap { public ManualResetEventSlim ResetEvent = new ManualResetEventSlim(); From 1c13b39104a14cf9dc9d7727a0460e8bfb63bab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Nov 2021 22:00:09 +0100 Subject: [PATCH 13/21] Revert incorrect ordering change --- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index dc38a9af69..f46cd405b5 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -85,11 +85,11 @@ namespace osu.Game.Beatmaps var rulesetInstance = ruleset.CreateInstance(); - IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); - if (rulesetInstance == null) throw new RulesetLoadException("Creating ruleset instance failed when attempting to create playable beatmap."); + IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); + // Check if the beatmap can be converted if (Beatmap.HitObjects.Count > 0 && !converter.CanConvert()) throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter})."); From 3de8125eac9e73127a183553587fadf04f391061 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Nov 2021 12:35:47 +0900 Subject: [PATCH 14/21] Update UI cases where repeat should not be handled --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 3 +++ osu.Game/Graphics/ScreenshotManager.cs | 3 +++ osu.Game/Graphics/UserInterface/BackButton.cs | 3 +++ osu.Game/Graphics/UserInterface/FocusedTextBox.cs | 3 +++ osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs | 3 +++ osu.Game/OsuGame.cs | 3 +++ osu.Game/Overlays/ChangelogOverlay.cs | 3 +++ osu.Game/Overlays/DialogOverlay.cs | 3 +++ osu.Game/Overlays/Music/MusicKeyBindingHandler.cs | 3 +++ osu.Game/Screens/Menu/ButtonSystem.cs | 3 +++ osu.Game/Screens/Menu/ExitConfirmOverlay.cs | 3 +++ osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 3 +++ .../Screens/OnlinePlay/Match/Components/CreateRoomButton.cs | 3 +++ .../Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs | 3 +++ osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 3 +++ osu.Game/Screens/Play/HUDOverlay.cs | 3 +++ osu.Game/Screens/Play/HotkeyExitOverlay.cs | 3 +++ osu.Game/Screens/Play/HotkeyRetryOverlay.cs | 3 +++ osu.Game/Screens/Play/SkipOverlay.cs | 3 +++ osu.Game/Screens/Ranking/ResultsScreen.cs | 3 +++ osu.Game/Screens/Select/SongSelect.cs | 3 +++ 21 files changed, 63 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 16ec7ab838..68351acd7e 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -90,6 +90,9 @@ namespace osu.Game.Graphics.Containers public virtual bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.Back: diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 1f7f93b3c3..a39d7bfb47 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -60,6 +60,9 @@ namespace osu.Game.Graphics public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.TakeScreenshot: diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index c965fbcf45..1b564ef1b4 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -64,6 +64,9 @@ namespace osu.Game.Graphics.UserInterface public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.Back: diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 88608bf43c..578ff3c618 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -75,6 +75,9 @@ namespace osu.Game.Graphics.UserInterface public virtual bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + if (!HasFocus) return false; if (e.Action == GlobalAction.Back) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs index 226c39c030..d1857dd174 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs @@ -58,6 +58,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + if (State.Value == Visibility.Hidden) return false; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 095add399c..1274d8d867 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1007,6 +1007,9 @@ namespace osu.Game public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + if (introScreen == null) return false; switch (e.Action) diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index ce12e9554d..fe611d0134 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -94,6 +94,9 @@ namespace osu.Game.Overlays public override bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.Back: diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 9db0f34d1b..9dea1ca00a 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -102,6 +102,9 @@ namespace osu.Game.Overlays public override bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.Select: diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index 18ec69e106..baee17fb00 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -32,6 +32,9 @@ namespace osu.Game.Overlays.Music public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + if (beatmap.Disabled) return false; diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 5f76176aab..feb6f6c92a 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -220,6 +220,9 @@ namespace osu.Game.Screens.Menu public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.Back: diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs index 364da2f887..a90b83c5fe 100644 --- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs +++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs @@ -21,6 +21,9 @@ namespace osu.Game.Screens.Menu public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + if (e.Action == GlobalAction.Back) { BeginConfirm(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 72574b729a..0d2b2249ef 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -134,6 +134,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + if (SelectedRoom.Value != Room) return false; diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs b/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs index 53131ab90e..81e1cb2406 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs @@ -19,6 +19,9 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + if (!Enabled.Value) return false; diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs index 6d14b95aec..435c9aca02 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs @@ -65,6 +65,9 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.Select: diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 850543136c..8e0a38aa1f 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -208,6 +208,9 @@ namespace osu.Game.Screens.Play.HUD public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.Back: diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 54c74a7177..b5c4433719 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -283,6 +283,9 @@ namespace osu.Game.Screens.Play public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.HoldForHUD: diff --git a/osu.Game/Screens/Play/HotkeyExitOverlay.cs b/osu.Game/Screens/Play/HotkeyExitOverlay.cs index 13b72ffaf6..9fe7d17cc7 100644 --- a/osu.Game/Screens/Play/HotkeyExitOverlay.cs +++ b/osu.Game/Screens/Play/HotkeyExitOverlay.cs @@ -12,6 +12,9 @@ namespace osu.Game.Screens.Play { public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + if (e.Action != GlobalAction.QuickExit) return false; BeginConfirm(); diff --git a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs index 308befe372..2812df8bbe 100644 --- a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs +++ b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs @@ -12,6 +12,9 @@ namespace osu.Game.Screens.Play { public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + if (e.Action != GlobalAction.QuickRetry) return false; BeginConfirm(); diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index c35548c6b4..84d5507dce 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -146,6 +146,9 @@ namespace osu.Game.Screens.Play public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.SkipCutscene: diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index dacc4f5f9e..e43f40e203 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -330,6 +330,9 @@ namespace osu.Game.Screens.Ranking public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.Select: diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index aef00a9b00..2c36bf5fc8 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -821,6 +821,9 @@ namespace osu.Game.Screens.Select public virtual bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + if (!this.IsCurrentScreen()) return false; switch (e.Action) From 7599efac3044cc813abe6ceed8c39f59841e4a76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Nov 2021 12:36:15 +0900 Subject: [PATCH 15/21] Update editor cases where repeat should not be handled --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 3 +++ osu.Game/Screens/Edit/Editor.cs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 29e3f12d03..130d7a015f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -239,6 +239,9 @@ namespace osu.Game.Screens.Edit.Compose.Components public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case PlatformAction.SelectAll: diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2a7e2c9cef..94b6e58b67 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -393,6 +393,9 @@ namespace osu.Game.Screens.Edit return true; case PlatformAction.Save: + if (e.Repeat) + return false; + Save(); return true; } @@ -457,6 +460,9 @@ namespace osu.Game.Screens.Edit public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + switch (e.Action) { case GlobalAction.Back: From d7b178ea37225d7618916a534a923dd7aa101588 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Nov 2021 12:36:52 +0900 Subject: [PATCH 16/21] Update test scenes which should not handle key repeat --- osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs | 3 +++ osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs | 3 +++ osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs | 3 +++ osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs index 883b8a1ae0..70a43fafb5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs @@ -83,6 +83,9 @@ namespace osu.Game.Tests.Visual.Gameplay public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + ReceivedAction = e.Action == TestAction.Down; return true; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index c8040f42f0..159d583fc0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -231,6 +231,9 @@ namespace osu.Game.Tests.Visual.Gameplay public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + box.Colour = Color4.White; return true; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index 08578168d6..1d4245308d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -164,6 +164,9 @@ namespace osu.Game.Tests.Visual.Gameplay public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + box.Colour = Color4.White; return true; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index b4de060578..ef870a32a9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -283,6 +283,9 @@ namespace osu.Game.Tests.Visual.Gameplay public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat) + return false; + box.Colour = Color4.White; return true; } From 66c307e0eecc4842adbdf9c52f0b56ae8bd1768b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Nov 2021 12:38:20 +0900 Subject: [PATCH 17/21] Remove usage of key repeat helper method --- osu.Game/Extensions/DrawableExtensions.cs | 27 --------- .../Overlays/Volume/VolumeControlReceptor.cs | 9 --- .../UI/Scrolling/DrawableScrollingRuleset.cs | 11 +--- .../Lounge/Components/RoomsContainer.cs | 39 +------------ osu.Game/Screens/Play/ReplayPlayer.cs | 17 +----- osu.Game/Screens/Select/BeatmapCarousel.cs | 58 ++----------------- 6 files changed, 10 insertions(+), 151 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 03cc345947..005804789e 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -1,11 +1,8 @@ // 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; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Bindings; -using osu.Framework.Threading; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osuTK; @@ -14,30 +11,6 @@ namespace osu.Game.Extensions { public static class DrawableExtensions { - public const double REPEAT_INTERVAL = 70; - public const double INITIAL_DELAY = 250; - - /// - /// Helper method that is used while doesn't support repetitions of . - /// Simulates repetitions by continually invoking a delegate according to the default key repeat rate. - /// - /// - /// The returned delegate can be cancelled to stop repeat events from firing (usually in ). - /// - /// The which is handling the repeat. - /// The to schedule repetitions on. - /// The to be invoked once immediately and with every repetition. - /// The delay imposed on the first repeat. Defaults to . - /// A which can be cancelled to stop the repeat events from firing. - public static ScheduledDelegate BeginKeyRepeat(this IKeyBindingHandler handler, Scheduler scheduler, Action action, double initialRepeatDelay = INITIAL_DELAY) - { - action(); - - ScheduledDelegate repeatDelegate = new ScheduledDelegate(action, handler.Time.Current + initialRepeatDelay, REPEAT_INTERVAL); - scheduler.Add(repeatDelegate); - return repeatDelegate; - } - /// /// Shakes this drawable. /// diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index 4129b46ce3..c601ce4130 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -6,8 +6,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Threading; -using osu.Game.Extensions; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Volume @@ -17,18 +15,12 @@ namespace osu.Game.Overlays.Volume public Func ActionRequested; public Func ScrollActionRequested; - private ScheduledDelegate keyRepeat; - public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) { case GlobalAction.DecreaseVolume: case GlobalAction.IncreaseVolume: - keyRepeat?.Cancel(); - keyRepeat = this.BeginKeyRepeat(Scheduler, () => ActionRequested?.Invoke(e.Action), 150); - return true; - case GlobalAction.ToggleMute: case GlobalAction.NextVolumeMeter: case GlobalAction.PreviousVolumeMeter: @@ -41,7 +33,6 @@ namespace osu.Game.Overlays.Volume public void OnReleased(KeyBindingReleaseEvent e) { - keyRepeat?.Cancel(); } protected override bool OnScroll(ScrollEvent e) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 2a9d3d1cf0..1f3a937311 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -14,7 +14,6 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; -using osu.Game.Extensions; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -204,11 +203,11 @@ namespace osu.Game.Rulesets.UI.Scrolling switch (e.Action) { case GlobalAction.IncreaseScrollSpeed: - scheduleScrollSpeedAdjustment(1); + AdjustScrollSpeed(1); return true; case GlobalAction.DecreaseScrollSpeed: - scheduleScrollSpeedAdjustment(-1); + AdjustScrollSpeed(-1); return true; } @@ -223,12 +222,6 @@ namespace osu.Game.Rulesets.UI.Scrolling scheduledScrollSpeedAdjustment = null; } - private void scheduleScrollSpeedAdjustment(int amount) - { - scheduledScrollSpeedAdjustment?.Cancel(); - scheduledScrollSpeedAdjustment = this.BeginKeyRepeat(Scheduler, () => AdjustScrollSpeed(amount)); - } - private class LocalScrollingInfo : IScrollingInfo { public IBindable Direction { get; } = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 32ae7cf859..54c762b8ce 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Threading; using osu.Game.Extensions; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; @@ -146,11 +145,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components switch (e.Action) { case GlobalAction.SelectNext: - beginRepeatSelection(() => selectNext(1), e.Action); + selectNext(1); return true; case GlobalAction.SelectPrevious: - beginRepeatSelection(() => selectNext(-1), e.Action); + selectNext(-1); return true; } @@ -159,40 +158,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public void OnReleased(KeyBindingReleaseEvent e) { - switch (e.Action) - { - case GlobalAction.SelectNext: - case GlobalAction.SelectPrevious: - endRepeatSelection(e.Action); - break; - } - } - - private ScheduledDelegate repeatDelegate; - private object lastRepeatSource; - - /// - /// Begin repeating the specified selection action. - /// - /// The action to perform. - /// The source of the action. Used in conjunction with to only cancel the correct action (most recently pressed key). - private void beginRepeatSelection(Action action, object source) - { - endRepeatSelection(); - - lastRepeatSource = source; - repeatDelegate = this.BeginKeyRepeat(Scheduler, action); - } - - private void endRepeatSelection(object source = null) - { - // only the most recent source should be able to cancel the current action. - if (source != null && !EqualityComparer.Default.Equals(lastRepeatSource, source)) - return; - - repeatDelegate?.Cancel(); - repeatDelegate = null; - lastRepeatSource = null; } private void selectNext(int direction) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 93054b7bb5..617374b4fd 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -7,9 +7,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Threading; using osu.Game.Beatmaps; -using osu.Game.Extensions; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -48,8 +46,6 @@ namespace osu.Game.Screens.Play protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false); - private ScheduledDelegate keyboardSeekDelegate; - public bool OnPressed(KeyBindingPressEvent e) { const double keyboard_seek_amount = 5000; @@ -57,13 +53,11 @@ namespace osu.Game.Screens.Play switch (e.Action) { case GlobalAction.SeekReplayBackward: - keyboardSeekDelegate?.Cancel(); - keyboardSeekDelegate = this.BeginKeyRepeat(Scheduler, () => keyboardSeek(-1)); + keyboardSeek(-1); return true; case GlobalAction.SeekReplayForward: - keyboardSeekDelegate?.Cancel(); - keyboardSeekDelegate = this.BeginKeyRepeat(Scheduler, () => keyboardSeek(1)); + keyboardSeek(1); return true; case GlobalAction.TogglePauseReplay: @@ -86,13 +80,6 @@ namespace osu.Game.Screens.Play public void OnReleased(KeyBindingReleaseEvent e) { - switch (e.Action) - { - case GlobalAction.SeekReplayBackward: - case GlobalAction.SeekReplayForward: - keyboardSeekDelegate?.Cancel(); - break; - } } } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 0c593ebea1..0fc7a99c96 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -18,7 +18,6 @@ using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; @@ -479,42 +478,27 @@ namespace osu.Game.Screens.Select switch (e.Key) { case Key.Left: - if (!e.Repeat) - beginRepeatSelection(() => SelectNext(-1), e.Key); + SelectNext(-1); return true; case Key.Right: - if (!e.Repeat) - beginRepeatSelection(() => SelectNext(), e.Key); + SelectNext(); return true; } return false; } - protected override void OnKeyUp(KeyUpEvent e) - { - switch (e.Key) - { - case Key.Left: - case Key.Right: - endRepeatSelection(e.Key); - break; - } - - base.OnKeyUp(e); - } - public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) { case GlobalAction.SelectNext: - beginRepeatSelection(() => SelectNext(1, false), e.Action); + SelectNext(1, false); return true; case GlobalAction.SelectPrevious: - beginRepeatSelection(() => SelectNext(-1, false), e.Action); + SelectNext(-1, false); return true; } @@ -523,40 +507,6 @@ namespace osu.Game.Screens.Select public void OnReleased(KeyBindingReleaseEvent e) { - switch (e.Action) - { - case GlobalAction.SelectNext: - case GlobalAction.SelectPrevious: - endRepeatSelection(e.Action); - break; - } - } - - private ScheduledDelegate repeatDelegate; - private object lastRepeatSource; - - /// - /// Begin repeating the specified selection action. - /// - /// The action to perform. - /// The source of the action. Used in conjunction with to only cancel the correct action (most recently pressed key). - private void beginRepeatSelection(Action action, object source) - { - endRepeatSelection(); - - lastRepeatSource = source; - repeatDelegate = this.BeginKeyRepeat(Scheduler, action); - } - - private void endRepeatSelection(object source = null) - { - // only the most recent source should be able to cancel the current action. - if (source != null && !EqualityComparer.Default.Equals(lastRepeatSource, source)) - return; - - repeatDelegate?.Cancel(); - repeatDelegate = null; - lastRepeatSource = null; } #endregion From cdb2a92f865c9a85a010e1010073b21c9392f120 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Nov 2021 12:57:51 +0900 Subject: [PATCH 18/21] Disable key repeat for all ruleset handling cases --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index a3f311c7a6..6564ff9e23 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -168,6 +168,8 @@ namespace osu.Game.Rulesets.UI public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer { + protected override bool HandleRepeats => false; + public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) { From 9ec6ecd3f6cf9fc147d910efaa6f670cd2bba98e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Nov 2021 13:39:25 +0900 Subject: [PATCH 19/21] 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 f7b7b6fb23..cbd1c92fbe 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 93fb729f46..6b722f00ea 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 3a2a3fd65e..00cbe2c849 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From a09589f8339f39e19edc3c5c62ee765cbabc430f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 17 Nov 2021 23:15:51 -0800 Subject: [PATCH 20/21] Fix score panels sometimes jumping when toggling statistics fast --- osu.Game/Screens/Ranking/ResultsScreen.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index e43f40e203..cb842ce4a0 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -311,10 +311,10 @@ namespace osu.Game.Screens.Ranking ScorePanelList.Attach(detachedPanel); // Move into its original location in the attached container first, then to the final location. - var origLocation = detachedPanel.Parent.ToLocalSpace(screenSpacePos); - detachedPanel.MoveTo(origLocation) + float origLocation = detachedPanel.Parent.ToLocalSpace(screenSpacePos).X; + detachedPanel.MoveToX(origLocation) .Then() - .MoveTo(new Vector2(0, origLocation.Y), 150, Easing.OutQuint); + .MoveToX(0, 150, Easing.OutQuint); // Show contracted panels. foreach (var contracted in ScorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) From bf8507c7b92dad894becf3d4eb41443cdff9ee5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Nov 2021 23:26:45 +0900 Subject: [PATCH 21/21] Only apply default timeout when debugger not attached --- osu.Game/Beatmaps/WorkingBeatmap.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index f46cd405b5..d2912229c6 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -80,7 +81,10 @@ namespace osu.Game.Beatmaps public virtual IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods = null, CancellationToken? cancellationToken = null) { - var token = cancellationToken ?? new CancellationTokenSource(10000).Token; + var token = cancellationToken ?? + // don't apply the default timeout when debugger is attached (may be breakpointing / debugging). + (Debugger.IsAttached ? new CancellationToken() : new CancellationTokenSource(10000).Token); + mods ??= Array.Empty(); var rulesetInstance = ruleset.CreateInstance();