From aab3277024a58e58cc8a31ed2a42c7ce73575413 Mon Sep 17 00:00:00 2001 From: emu1337 Date: Mon, 11 Oct 2021 03:12:57 +0200 Subject: [PATCH 001/363] changed speed acc scaling to be closer to worst case scenario --- .../Difficulty/OsuDifficultyAttributes.cs | 1 + .../Difficulty/OsuDifficultyCalculator.cs | 2 ++ .../Difficulty/OsuPerformanceCalculator.cs | 9 ++++++++- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 16 +++++++++++++++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index bd4c0f2ad5..1f8b66edb7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public double AimStrain { get; set; } public double SpeedStrain { get; set; } + public double SpeedNoteCount { get; set; } public double FlashlightRating { get; set; } public double ApproachRate { get; set; } public double OverallDifficulty { get; set; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 790aa0eb7d..4160da9d5e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double speedNotes = (skills[1] as Speed).RelevantNoteCount(); if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; @@ -72,6 +73,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty Mods = mods, AimStrain = aimRating, SpeedStrain = speedRating, + SpeedNoteCount = speedNotes, FlashlightRating = flashlightRating, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 4e4dbc02a1..140c49393b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -168,8 +168,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); } + // Calculate accuracy assuming the worst case scenario + double relevantTotalDiff = totalHits - Attributes.SpeedNoteCount; + double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); + double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); + double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); + double relevantAccuracy = (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (Attributes.SpeedNoteCount * 6.0); + // Scale the speed value with accuracy and OD. - speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2); + speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2); // Scale the speed value with # of 50s to punish doubletapping. speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index cae6b8e01c..f8c1042a10 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -7,6 +7,8 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; using osu.Framework.Utils; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -33,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly double greatWindow; + private List objectStrains = new List(); + public Speed(Mod[] mods, double hitWindowGreat) : base(mods) { @@ -170,7 +174,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentRhythm = calculateRhythmBonus(current); - return currentStrain * currentRhythm; + double totalStrain = currentStrain * currentRhythm; + + objectStrains.Add(totalStrain); + + return totalStrain; + } + + public double RelevantNoteCount() + { + double maxStrain = objectStrains.Max(); + return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Pow(Math.E, -(next/maxStrain * 12.0 - 6.0))))); } } } From d7483a6c5da7ace483c9e6cac31edf4f3ca0a67c Mon Sep 17 00:00:00 2001 From: emu1337 Date: Mon, 11 Oct 2021 19:58:03 +0200 Subject: [PATCH 002/363] changed pow to exp function --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index f8c1042a10..f477f8e17d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public double RelevantNoteCount() { double maxStrain = objectStrains.Max(); - return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Pow(Math.E, -(next/maxStrain * 12.0 - 6.0))))); + return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Exp(-(next/maxStrain * 12.0 - 6.0))))); } } } From f3eaa950416971ccfd10f8e492c4b8b61b53e92d Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 14 Jan 2022 23:16:07 +0100 Subject: [PATCH 003/363] Move input handler settings creation to `OsuGameBase` --- osu.Desktop/OsuGameDesktop.cs | 25 +++++++++++ osu.Game/OsuGameBase.cs | 21 ++++++++++ .../Settings/Sections/InputSection.cs | 41 ++----------------- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index b234207848..a0c2a80bb8 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -20,8 +20,15 @@ using osu.Framework.Screens; using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; +using osu.Framework.Input.Handlers; +using osu.Framework.Input.Handlers.Joystick; +using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Threading; using osu.Game.IO; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; +using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Desktop { @@ -156,6 +163,24 @@ namespace osu.Desktop desktopWindow.DragDrop += f => fileDrop(new[] { f }); } + public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case ITabletHandler th: + return new TabletSettings(th); + + case MouseHandler mh: + return new MouseSettings(mh); + + case JoystickHandler _: + return new InputSection.HandlerSection(handler); + + default: + return base.CreateSettingsSubsectionFor(handler); + } + } + private readonly List importableFiles = new List(); private ScheduledDelegate importSchedule; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9256514a0a..0059fc2204 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -17,6 +17,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; +using osu.Framework.Input.Handlers; +using osu.Framework.Input.Handlers.Midi; using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Framework.Platform; @@ -35,6 +37,8 @@ using osu.Game.Online.Chat; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Overlays; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; using osu.Game.Resources; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -448,6 +452,23 @@ namespace osu.Game protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); + /// + /// Creates an input settings subsection for an . + /// + /// Should be overriden per-platform to provide settings for platform-specific handlers. + public virtual SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case MidiHandler _: + return new InputSection.HandlerSection(handler); + + // return null for handlers that shouldn't have settings. + default: + return null; + } + } + private void onRulesetChanged(ValueChangedEvent r) { if (r.NewValue?.Available != true) diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index d282ba5318..77076ae920 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -5,10 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Handlers; -using osu.Framework.Input.Handlers.Joystick; -using osu.Framework.Input.Handlers.Midi; -using osu.Framework.Input.Handlers.Mouse; -using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Localisation; @@ -22,9 +18,6 @@ namespace osu.Game.Overlays.Settings.Sections public override LocalisableString Header => InputSettingsStrings.InputSectionHeader; - [Resolved] - private GameHost host { get; set; } - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.Keyboard @@ -36,7 +29,7 @@ namespace osu.Game.Overlays.Settings.Sections } [BackgroundDependencyLoader] - private void load() + private void load(GameHost host, OsuGameBase game) { Children = new Drawable[] { @@ -45,42 +38,14 @@ namespace osu.Game.Overlays.Settings.Sections foreach (var handler in host.AvailableInputHandlers) { - var handlerSection = createSectionFor(handler); + var handlerSection = game.CreateSettingsSubsectionFor(handler); if (handlerSection != null) Add(handlerSection); } } - private SettingsSubsection createSectionFor(InputHandler handler) - { - SettingsSubsection section; - - switch (handler) - { - // ReSharper disable once SuspiciousTypeConversion.Global (net standard fuckery) - case ITabletHandler th: - section = new TabletSettings(th); - break; - - case MouseHandler mh: - section = new MouseSettings(mh); - break; - - // whitelist the handlers which should be displayed to avoid any weird cases of users touching settings they shouldn't. - case JoystickHandler _: - case MidiHandler _: - section = new HandlerSection(handler); - break; - - default: - return null; - } - - return section; - } - - private class HandlerSection : SettingsSubsection + public class HandlerSection : SettingsSubsection { private readonly InputHandler handler; From 037e56f13eca292e9b8b4a43a0eb4da262ac0fa8 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 15 Jan 2022 14:38:38 +0100 Subject: [PATCH 004/363] Add Android mouse settings --- osu.Android/AndroidMouseSettings.cs | 97 +++++++++++++++++++ osu.Android/OsuGameAndroid.cs | 22 +++++ osu.Android/osu.Android.csproj | 1 + .../Settings/Sections/Input/MouseSettings.cs | 4 +- 4 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 osu.Android/AndroidMouseSettings.cs diff --git a/osu.Android/AndroidMouseSettings.cs b/osu.Android/AndroidMouseSettings.cs new file mode 100644 index 0000000000..7dff929cd4 --- /dev/null +++ b/osu.Android/AndroidMouseSettings.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Android.OS; +using osu.Framework.Allocation; +using osu.Framework.Android.Input; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Localisation; +using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections.Input; + +namespace osu.Android +{ + public class AndroidMouseSettings : SettingsSubsection + { + private readonly AndroidMouseHandler mouseHandler; + + protected override LocalisableString Header => MouseSettingsStrings.Mouse; + + private Bindable handlerSensitivity; + + private Bindable localSensitivity; + + private Bindable relativeMode; + + public AndroidMouseSettings(AndroidMouseHandler mouseHandler) + { + this.mouseHandler = mouseHandler; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig) + { + // use local bindable to avoid changing enabled state of game host's bindable. + handlerSensitivity = mouseHandler.Sensitivity.GetBoundCopy(); + localSensitivity = handlerSensitivity.GetUnboundCopy(); + + relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy(); + + // High precision/pointer capture is only available on Android 8.0 and up + if (Build.VERSION.SdkInt >= BuildVersionCodes.O) + { + AddRange(new Drawable[] + { + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.HighPrecisionMouse, + TooltipText = MouseSettingsStrings.HighPrecisionMouseTooltip, + Current = relativeMode, + Keywords = new[] { @"raw", @"input", @"relative", @"cursor", @"captured", @"pointer" }, + }, + new MouseSettings.SensitivitySetting + { + LabelText = MouseSettingsStrings.CursorSensitivity, + Current = localSensitivity, + }, + }); + } + + AddRange(new Drawable[] + { + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust, + TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel), + }, + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseButtons, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons), + }, + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + relativeMode.BindValueChanged(relative => localSensitivity.Disabled = !relative.NewValue, true); + + handlerSensitivity.BindValueChanged(val => + { + bool disabled = localSensitivity.Disabled; + + localSensitivity.Disabled = false; + localSensitivity.Value = val.NewValue; + localSensitivity.Disabled = disabled; + }, true); + + localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue); + } + } +} diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 050bf2b787..ba2527d71e 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -5,7 +5,11 @@ using System; using Android.App; using Android.OS; using osu.Framework.Allocation; +using osu.Framework.Android.Input; +using osu.Framework.Input.Handlers; +using osu.Framework.Platform; using osu.Game; +using osu.Game.Overlays.Settings; using osu.Game.Updater; using osu.Game.Utils; using Xamarin.Essentials; @@ -73,10 +77,28 @@ namespace osu.Android LoadComponentAsync(new GameplayScreenRotationLocker(), Add); } + public override void SetHost(GameHost host) + { + base.SetHost(host); + host.Window.CursorState |= CursorState.Hidden; + } + protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo(); + public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case AndroidMouseHandler mh: + return new AndroidMouseSettings(mh); + + default: + return base.CreateSettingsSubsectionFor(handler); + } + } + private class AndroidBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index fc50ca9fa1..90b02c527b 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -26,6 +26,7 @@ true + diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 4235dc0a05..6bb97c1137 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); } - private class SensitivitySetting : SettingsSlider + public class SensitivitySetting : SettingsSlider { public SensitivitySetting() { @@ -133,7 +133,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input } } - private class SensitivitySlider : OsuSliderBar + public class SensitivitySlider : OsuSliderBar { public override LocalisableString TooltipText => Current.Disabled ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust : $"{base.TooltipText}x"; } From d4af8498af6795213d648844d1c11da99f65f4e6 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 15 Jan 2022 14:48:41 +0100 Subject: [PATCH 005/363] Add iOS mouse settings Functionality is currently limited to some OsuSettings, but will expand in the future when high precision mouse is added. --- osu.iOS/IOSMouseSettings.cs | 36 ++++++++++++++++++++++++++++++++++++ osu.iOS/OsuGameIOS.cs | 15 +++++++++++++++ osu.iOS/osu.iOS.csproj | 1 + 3 files changed, 52 insertions(+) create mode 100644 osu.iOS/IOSMouseSettings.cs diff --git a/osu.iOS/IOSMouseSettings.cs b/osu.iOS/IOSMouseSettings.cs new file mode 100644 index 0000000000..1979a881f7 --- /dev/null +++ b/osu.iOS/IOSMouseSettings.cs @@ -0,0 +1,36 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Localisation; +using osu.Game.Overlays.Settings; + +namespace osu.iOS +{ + public class IOSMouseSettings : SettingsSubsection + { + protected override LocalisableString Header => MouseSettingsStrings.Mouse; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust, + TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel), + }, + new SettingsCheckbox + { + LabelText = MouseSettingsStrings.DisableMouseButtons, + Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons), + }, + }; + } + } +} diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 702aef45f5..c3f7033752 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -3,7 +3,10 @@ using System; using Foundation; +using osu.Framework.Input.Handlers; +using osu.Framework.iOS.Input; using osu.Game; +using osu.Game.Overlays.Settings; using osu.Game.Updater; using osu.Game.Utils; using Xamarin.Essentials; @@ -18,6 +21,18 @@ namespace osu.iOS protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); + public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler) + { + switch (handler) + { + case IOSMouseHandler _: + return new IOSMouseSettings(); + + default: + return base.CreateSettingsSubsectionFor(handler); + } + } + private class IOSBatteryInfo : BatteryInfo { public override double ChargeLevel => Battery.ChargeLevel; diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 1203c3659b..b9da874f30 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -23,6 +23,7 @@ + From 7e7716f942a5ef2df2e49c8783c9f1fa829614a3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 15:40:11 +0900 Subject: [PATCH 006/363] Support undo/redo for control points --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 59 ++++++----- .../Edit/LegacyEditorBeatmapPatcher.cs | 100 ++++++++++++------ 3 files changed, 105 insertions(+), 56 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index ff13e61360..25551d1ef6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -143,7 +143,7 @@ namespace osu.Game.Beatmaps.Formats protected string CleanFilename(string path) => path.Trim('"').ToStandardisedPath(); - protected enum Section + public enum Section { General, Editor, diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index c9449f3259..577a8291ad 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -71,31 +71,7 @@ namespace osu.Game.Screens.Edit public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null, BeatmapInfo beatmapInfo = null) { PlayableBeatmap = playableBeatmap; - - // ensure we are not working with legacy control points. - // if we leave the legacy points around they will be applied over any local changes on - // ApplyDefaults calls. this should eventually be removed once the default logic is moved to the decoder/converter. - if (PlayableBeatmap.ControlPointInfo is LegacyControlPointInfo) - { - var newControlPoints = new ControlPointInfo(); - - foreach (var controlPoint in PlayableBeatmap.ControlPointInfo.AllControlPoints) - { - switch (controlPoint) - { - case DifficultyControlPoint _: - case SampleControlPoint _: - // skip legacy types. - continue; - - default: - newControlPoints.Add(controlPoint.Time, controlPoint); - break; - } - } - - playableBeatmap.ControlPointInfo = newControlPoints; - } + PlayableBeatmap.ControlPointInfo = ConvertControlPoints(PlayableBeatmap.ControlPointInfo); this.beatmapInfo = beatmapInfo ?? playableBeatmap.BeatmapInfo; @@ -108,6 +84,39 @@ namespace osu.Game.Screens.Edit trackStartTime(obj); } + /// + /// Converts a such that the resultant is non-legacy. + /// + /// The to convert. + /// The non-legacy . is returned if already non-legacy. + public static ControlPointInfo ConvertControlPoints(ControlPointInfo incoming) + { + // ensure we are not working with legacy control points. + // if we leave the legacy points around they will be applied over any local changes on + // ApplyDefaults calls. this should eventually be removed once the default logic is moved to the decoder/converter. + if (!(incoming is LegacyControlPointInfo)) + return incoming; + + var newControlPoints = new ControlPointInfo(); + + foreach (var controlPoint in incoming.AllControlPoints) + { + switch (controlPoint) + { + case DifficultyControlPoint _: + case SampleControlPoint _: + // skip legacy types. + continue; + + default: + newControlPoints.Add(controlPoint.Time, controlPoint); + break; + } + } + + return newControlPoints; + } + public BeatmapInfo BeatmapInfo { get => beatmapInfo; diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 3ed2a7efe2..ba9a7ed1a3 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -7,9 +7,11 @@ using System.Diagnostics; using System.IO; using System.Text; using DiffPlex; +using DiffPlex.Model; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Skinning; using Decoder = osu.Game.Beatmaps.Formats.Decoder; @@ -32,61 +34,99 @@ namespace osu.Game.Screens.Edit { // Diff the beatmaps var result = new Differ().CreateLineDiffs(readString(currentState), readString(newState), true, false); + IBeatmap newBeatmap = null; + + editorBeatmap.BeginChange(); + processHitObjects(result, () => newBeatmap ??= readBeatmap(newState)); + processTimingPoints(result, () => newBeatmap ??= readBeatmap(newState)); + editorBeatmap.EndChange(); + } + + private void processTimingPoints(DiffResult result, Func getNewBeatmap) + { + findChangedIndices(result, LegacyDecoder.Section.TimingPoints, out var removedIndices, out var addedIndices); + + if (removedIndices.Count == 0 && addedIndices.Count == 0) + return; + + // Due to conversion from legacy to non-legacy control points, it becomes difficult to diff control points correctly. + // So instead _all_ control points are reloaded if _any_ control point is changed. + + var newControlPoints = EditorBeatmap.ConvertControlPoints(getNewBeatmap().ControlPointInfo); + + editorBeatmap.ControlPointInfo.Clear(); + foreach (var point in newControlPoints.AllControlPoints) + editorBeatmap.ControlPointInfo.Add(point.Time, point); + } + + private void processHitObjects(DiffResult result, Func getNewBeatmap) + { + findChangedIndices(result, LegacyDecoder.Section.HitObjects, out var removedIndices, out var addedIndices); + + foreach (int removed in removedIndices) + editorBeatmap.RemoveAt(removed); + + if (addedIndices.Count > 0) + { + var newBeatmap = getNewBeatmap(); + + foreach (int i in addedIndices) + editorBeatmap.Insert(i, newBeatmap.HitObjects[i]); + } + } + + private void findChangedIndices(DiffResult result, LegacyDecoder.Section section, out List removedIndices, out List addedIndices) + { + removedIndices = new List(); + addedIndices = new List(); // Find the index of [HitObject] sections. Lines changed prior to this index are ignored. - int oldHitObjectsIndex = Array.IndexOf(result.PiecesOld, "[HitObjects]"); - int newHitObjectsIndex = Array.IndexOf(result.PiecesNew, "[HitObjects]"); + int oldSectionStartIndex = Array.IndexOf(result.PiecesOld, $"[{section}]"); + int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith(@"[", StringComparison.Ordinal)); - Debug.Assert(oldHitObjectsIndex >= 0); - Debug.Assert(newHitObjectsIndex >= 0); + if (oldSectionEndIndex == -1) + oldSectionEndIndex = result.PiecesOld.Length; - var toRemove = new List(); - var toAdd = new List(); + int newSectionStartIndex = Array.IndexOf(result.PiecesNew, $"[{section}]"); + int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith(@"[", StringComparison.Ordinal)); + + if (newSectionEndIndex == -1) + newSectionEndIndex = result.PiecesOld.Length; + + Debug.Assert(oldSectionStartIndex >= 0); + Debug.Assert(newSectionStartIndex >= 0); foreach (var block in result.DiffBlocks) { - // Removed hitobjects + // Removed indices for (int i = 0; i < block.DeleteCountA; i++) { - int hoIndex = block.DeleteStartA + i - oldHitObjectsIndex - 1; + int objectIndex = block.DeleteStartA + i; - if (hoIndex < 0) + if (objectIndex <= oldSectionStartIndex || objectIndex >= oldSectionEndIndex) continue; - toRemove.Add(hoIndex); + removedIndices.Add(objectIndex - oldSectionStartIndex - 1); } - // Added hitobjects + // Added indices for (int i = 0; i < block.InsertCountB; i++) { - int hoIndex = block.InsertStartB + i - newHitObjectsIndex - 1; + int objectIndex = block.InsertStartB + i; - if (hoIndex < 0) + if (objectIndex <= newSectionStartIndex || objectIndex >= newSectionEndIndex) continue; - toAdd.Add(hoIndex); + addedIndices.Add(objectIndex - newSectionStartIndex - 1); } } // Sort the indices to ensure that removal + insertion indices don't get jumbled up post-removal or post-insertion. // This isn't strictly required, but the differ makes no guarantees about order. - toRemove.Sort(); - toAdd.Sort(); + removedIndices.Sort(); + addedIndices.Sort(); - editorBeatmap.BeginChange(); - - // Apply the changes. - for (int i = toRemove.Count - 1; i >= 0; i--) - editorBeatmap.RemoveAt(toRemove[i]); - - if (toAdd.Count > 0) - { - IBeatmap newBeatmap = readBeatmap(newState); - foreach (int i in toAdd) - editorBeatmap.Insert(i, newBeatmap.HitObjects[i]); - } - - editorBeatmap.EndChange(); + removedIndices.Reverse(); } private string readString(byte[] state) => Encoding.UTF8.GetString(state); From 776e7c0c718e531874247c5c5dca8c0294cd2a04 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 15:40:18 +0900 Subject: [PATCH 007/363] Work around performance issues --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index f498aa917e..015e7f5bc1 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -137,8 +137,12 @@ namespace osu.Game.Screens.Edit.Timing controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => { - table.ControlGroups = controlPointGroups; - changeHandler?.SaveState(); + // This AddOnce() works around performance issues from the LegacyEditorBeatmapPatcher re-initialising all control points every undo & redo. + Scheduler.AddOnce(() => + { + table.ControlGroups = controlPointGroups; + changeHandler?.SaveState(); + }); }, true); } From 5a18547342d1c7a242052fb28ecc72159e128f0d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 15:58:11 +0900 Subject: [PATCH 008/363] Compare by `char` Co-authored-by: Berkan Diler --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index ba9a7ed1a3..c668778be2 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -82,13 +82,13 @@ namespace osu.Game.Screens.Edit // Find the index of [HitObject] sections. Lines changed prior to this index are ignored. int oldSectionStartIndex = Array.IndexOf(result.PiecesOld, $"[{section}]"); - int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith(@"[", StringComparison.Ordinal)); + int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith('[')); if (oldSectionEndIndex == -1) oldSectionEndIndex = result.PiecesOld.Length; int newSectionStartIndex = Array.IndexOf(result.PiecesNew, $"[{section}]"); - int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith(@"[", StringComparison.Ordinal)); + int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith('[')); if (newSectionEndIndex == -1) newSectionEndIndex = result.PiecesOld.Length; From 285e5abb417e43fb8b95054edeba2d2a05d6e90d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 16:55:46 +0900 Subject: [PATCH 009/363] Fix incorrect fallback value --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index c668778be2..939d319405 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Edit int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith('[')); if (newSectionEndIndex == -1) - newSectionEndIndex = result.PiecesOld.Length; + newSectionEndIndex = result.PiecesNew.Length; Debug.Assert(oldSectionStartIndex >= 0); Debug.Assert(newSectionStartIndex >= 0); From a8286bdf0486b5c18e480afd2facf77c24714b58 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 16:56:08 +0900 Subject: [PATCH 010/363] Fix assertion failures --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 939d319405..b56d990d75 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -82,14 +82,18 @@ namespace osu.Game.Screens.Edit // Find the index of [HitObject] sections. Lines changed prior to this index are ignored. int oldSectionStartIndex = Array.IndexOf(result.PiecesOld, $"[{section}]"); - int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith('[')); + if (oldSectionStartIndex == -1) + return; + int oldSectionEndIndex = Array.FindIndex(result.PiecesOld, oldSectionStartIndex + 1, s => s.StartsWith('[')); if (oldSectionEndIndex == -1) oldSectionEndIndex = result.PiecesOld.Length; int newSectionStartIndex = Array.IndexOf(result.PiecesNew, $"[{section}]"); - int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith('[')); + if (newSectionStartIndex == -1) + return; + int newSectionEndIndex = Array.FindIndex(result.PiecesNew, newSectionStartIndex + 1, s => s.StartsWith('[')); if (newSectionEndIndex == -1) newSectionEndIndex = result.PiecesNew.Length; From c178e5d5923cdbcc6ace0adad7b475c3865f1a66 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 16:58:43 +0900 Subject: [PATCH 011/363] Add explanatory comment --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index b56d990d75..66ece8fcfa 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -130,6 +130,8 @@ namespace osu.Game.Screens.Edit removedIndices.Sort(); addedIndices.Sort(); + // The expected usage of this returned list is to iterate from the start to the end of the list, such that + // these indices need to appear in reverse order for the usage to not have to deal with decrementing indices. removedIndices.Reverse(); } From f680f4d26bce5cc1d3fbc07946a8256d200b6aab Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jun 2022 17:36:32 +0900 Subject: [PATCH 012/363] Apply refactorings from review --- .../Screens/Edit/LegacyEditorBeatmapPatcher.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 66ece8fcfa..00cb95dd79 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Text; using DiffPlex; @@ -63,8 +62,8 @@ namespace osu.Game.Screens.Edit { findChangedIndices(result, LegacyDecoder.Section.HitObjects, out var removedIndices, out var addedIndices); - foreach (int removed in removedIndices) - editorBeatmap.RemoveAt(removed); + for (int i = removedIndices.Count - 1; i >= 0; i--) + editorBeatmap.RemoveAt(removedIndices[i]); if (addedIndices.Count > 0) { @@ -80,7 +79,7 @@ namespace osu.Game.Screens.Edit removedIndices = new List(); addedIndices = new List(); - // Find the index of [HitObject] sections. Lines changed prior to this index are ignored. + // Find the start and end indices of the relevant section headers in both the old and the new beatmap file. Lines changed outside of the modified ranges are ignored. int oldSectionStartIndex = Array.IndexOf(result.PiecesOld, $"[{section}]"); if (oldSectionStartIndex == -1) return; @@ -97,9 +96,6 @@ namespace osu.Game.Screens.Edit if (newSectionEndIndex == -1) newSectionEndIndex = result.PiecesNew.Length; - Debug.Assert(oldSectionStartIndex >= 0); - Debug.Assert(newSectionStartIndex >= 0); - foreach (var block in result.DiffBlocks) { // Removed indices @@ -129,10 +125,6 @@ namespace osu.Game.Screens.Edit // This isn't strictly required, but the differ makes no guarantees about order. removedIndices.Sort(); addedIndices.Sort(); - - // The expected usage of this returned list is to iterate from the start to the end of the list, such that - // these indices need to appear in reverse order for the usage to not have to deal with decrementing indices. - removedIndices.Reverse(); } private string readString(byte[] state) => Encoding.UTF8.GetString(state); From 1f78a4fadd36ebfd1145ea162d6b47826cec664c Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 17 Jun 2022 16:56:09 +0900 Subject: [PATCH 013/363] New audio feedback for metronome --- .../Screens/Edit/Timing/MetronomeDisplay.cs | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 2ecd66a05f..33325951e5 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -33,7 +33,9 @@ namespace osu.Game.Screens.Edit.Timing private IAdjustableClock metronomeClock; - private Sample clunk; + private Sample tick; + private Sample tickDownbeat; + private Sample latch; [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } @@ -43,7 +45,9 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load(AudioManager audio) { - clunk = audio.Samples.Get(@"Multiplayer/countdown-tick"); + tick = audio.Samples.Get(@"UI/metronome-tick"); + tickDownbeat = audio.Samples.Get(@"UI/metronome-tick-downbeat"); + latch = audio.Samples.Get(@"UI/metronome-latch"); const float taper = 25; const float swing_vertical_offset = -23; @@ -248,14 +252,27 @@ namespace osu.Game.Screens.Edit.Timing if (BeatSyncSource.Clock?.IsRunning != true && isSwinging) { swing.ClearTransforms(true); + stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); + isSwinging = false; + + // instantly latch if pendulum arm is close enough to center (to prevent awkward delayed playback of latch sound) + if (Precision.AlmostEquals(swing.Rotation, 0, 1)) + { + swing.RotateTo(0); + latch?.Play(); + return; + } using (swing.BeginDelayedSequence(350)) { swing.RotateTo(0, 1000, Easing.OutQuint); - stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); + swing.Delay(250).Schedule(() => + { + // prevent playing latch sound if metronome has started back up again + if (!isSwinging) + latch?.Play(); + }); } - - isSwinging = false; } } @@ -286,13 +303,13 @@ namespace osu.Game.Screens.Edit.Timing if (!EnableClicking) return; - var channel = clunk?.GetChannel(); + var channel = beatIndex % timingPoint.TimeSignature.Numerator == 0 ? tickDownbeat?.GetChannel() : tick?.GetChannel(); - if (channel != null) - { - channel.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); - channel.Play(); - } + if (channel == null) + return; + + channel.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); + channel.Play(); }); } } From 48ffd6aeb5c131a120a9c82e889348ebf3d21dfb Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Fri, 17 Jun 2022 11:28:58 -0400 Subject: [PATCH 014/363] stop component transform when skineditor is hidden --- .../Skinning/Editor/SkinSelectionHandler.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 2e2122f7c2..2496d7c657 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -11,6 +11,7 @@ using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; @@ -25,8 +26,17 @@ namespace osu.Game.Skinning.Editor [Resolved] private SkinEditor skinEditor { get; set; } + /// + /// Whether the 's state allows selected blueprints to undergo transformations. + /// Used to prevent operations from being performed on unloaded blueprints. + /// + private bool allowHandling => skinEditor.State.Value == Visibility.Visible; + public override bool HandleRotation(float angle) { + if (!allowHandling) + return false; + if (SelectedBlueprints.Count == 1) { // for single items, rotate around the origin rather than the selection centre. @@ -53,6 +63,9 @@ namespace osu.Game.Skinning.Editor public override bool HandleScale(Vector2 scale, Anchor anchor) { + if (!allowHandling) + return false; + // convert scale to screen space scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero); @@ -130,6 +143,9 @@ namespace osu.Game.Skinning.Editor public override bool HandleFlip(Direction direction, bool flipOverOrigin) { + if (!allowHandling) + return false; + var selectionQuad = getSelectionQuad(); Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1); @@ -150,6 +166,9 @@ namespace osu.Game.Skinning.Editor public override bool HandleMovement(MoveSelectionEvent moveEvent) { + if (!allowHandling) + return false; + foreach (var c in SelectedBlueprints) { var item = c.Item; From 9fe763613868c01e6570bb425615446d7aeceb59 Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Fri, 17 Jun 2022 23:38:22 +0800 Subject: [PATCH 015/363] Use accuracy for pp calculation --- .../Difficulty/ManiaPerformanceCalculator.cs | 58 +++---------------- 1 file changed, 9 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index eb58eb7f21..02c118ae1f 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -15,15 +15,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public class ManiaPerformanceCalculator : PerformanceCalculator { - // Score after being scaled by non-difficulty-increasing mods - private double scaledScore; - + private int countPerfect; private int countGreat; private int countGood; private int countOk; private int countMeh; private int countMiss; + private double scoreAccuracy; public ManiaPerformanceCalculator() : base(new ManiaRuleset()) @@ -34,23 +33,17 @@ namespace osu.Game.Rulesets.Mania.Difficulty { var maniaAttributes = (ManiaDifficultyAttributes)attributes; - scaledScore = score.TotalScore; countPerfect = score.Statistics.GetValueOrDefault(HitResult.Perfect); countGreat = score.Statistics.GetValueOrDefault(HitResult.Great); countGood = score.Statistics.GetValueOrDefault(HitResult.Good); countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - - if (maniaAttributes.ScoreMultiplier > 0) - { - // Scale score up, so it's comparable to other keymods - scaledScore *= 1.0 / maniaAttributes.ScoreMultiplier; - } + scoreAccuracy = customAccuracy; // Arbitrary initial value for scaling pp in order to standardize distributions across game modes. // The specific number has no intrinsic meaning and can be adjusted as needed. - double multiplier = 0.8; + double multiplier = 8.0; if (score.Mods.Any(m => m is ModNoFail)) multiplier *= 0.9; @@ -58,58 +51,25 @@ namespace osu.Game.Rulesets.Mania.Difficulty multiplier *= 0.5; double difficultyValue = computeDifficultyValue(maniaAttributes); - double accValue = computeAccuracyValue(difficultyValue, maniaAttributes); - double totalValue = - Math.Pow( - Math.Pow(difficultyValue, 1.1) + - Math.Pow(accValue, 1.1), 1.0 / 1.1 - ) * multiplier; + double totalValue = difficultyValue * multiplier; return new ManiaPerformanceAttributes { Difficulty = difficultyValue, - Accuracy = accValue, - ScaledScore = scaledScore, Total = totalValue }; } private double computeDifficultyValue(ManiaDifficultyAttributes attributes) { - double difficultyValue = Math.Pow(5 * Math.Max(1, attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; - - difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); - - if (scaledScore <= 500000) - difficultyValue = 0; - else if (scaledScore <= 600000) - difficultyValue *= (scaledScore - 500000) / 100000 * 0.3; - else if (scaledScore <= 700000) - difficultyValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25; - else if (scaledScore <= 800000) - difficultyValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20; - else if (scaledScore <= 900000) - difficultyValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15; - else - difficultyValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1; + double difficultyValue = Math.Pow(Math.Max(attributes.StarRating - 0.15, 0.05), 2.2) // Star rating to pp curve + * Math.Max(0, 5 * scoreAccuracy - 4) // Accuracy curve + * (1 + 0.1 * Math.Min(1, totalHits / 1500)); // Length bonus return difficultyValue; } - private double computeAccuracyValue(double difficultyValue, ManiaDifficultyAttributes attributes) - { - if (attributes.GreatHitWindow <= 0) - return 0; - - // Lots of arbitrary values from testing. - // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution - double accuracyValue = Math.Max(0.0, 0.2 - (attributes.GreatHitWindow - 34) * 0.006667) - * difficultyValue - * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1); - - return accuracyValue; - } - private double totalHits => countPerfect + countOk + countGreat + countGood + countMeh + countMiss; + private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); } } From e238bcc6c635cb7efd04d790cb92cb540002e9da Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Fri, 17 Jun 2022 23:44:10 +0800 Subject: [PATCH 016/363] Remove unneeded attributes --- .../Difficulty/ManiaDifficultyAttributes.cs | 8 ------ .../Difficulty/ManiaDifficultyCalculator.cs | 28 ------------------- .../Difficulty/ManiaPerformanceAttributes.cs | 7 ----- 3 files changed, 43 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 8a5161be79..ffef8a50ad 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -20,12 +20,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty [JsonProperty("great_hit_window")] public double GreatHitWindow { get; set; } - /// - /// The score multiplier applied via score-reducing mods. - /// - [JsonProperty("score_multiplier")] - public double ScoreMultiplier { get; set; } - public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes() { foreach (var v in base.ToDatabaseAttributes()) @@ -34,7 +28,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty yield return (ATTRIB_ID_MAX_COMBO, MaxCombo); yield return (ATTRIB_ID_DIFFICULTY, StarRating); yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); - yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier); } public override void FromDatabaseAttributes(IReadOnlyDictionary values) @@ -44,7 +37,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; StarRating = values[ATTRIB_ID_DIFFICULTY]; GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; - ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER]; } } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 8002410f70..178094476f 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -53,7 +53,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty // In osu-stable mania, rate-adjustment mods don't affect the hit window. // This is done the way it is to introduce fractional differences in order to match osu-stable for the time being. GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate), - ScoreMultiplier = getScoreMultiplier(mods), MaxCombo = beatmap.HitObjects.Sum(maxComboForObject) }; } @@ -147,32 +146,5 @@ namespace osu.Game.Rulesets.Mania.Difficulty return value; } } - - private double getScoreMultiplier(Mod[] mods) - { - double scoreMultiplier = 1; - - foreach (var m in mods) - { - switch (m) - { - case ManiaModNoFail _: - case ManiaModEasy _: - case ManiaModHalfTime _: - scoreMultiplier *= 0.5; - break; - } - } - - var maniaBeatmap = (ManiaBeatmap)Beatmap; - int diff = maniaBeatmap.TotalColumns - maniaBeatmap.OriginalTotalColumns; - - if (diff > 0) - scoreMultiplier *= 0.9; - else if (diff < 0) - scoreMultiplier *= 0.9 + 0.04 * diff; - - return scoreMultiplier; - } } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs index f5abb465c4..01474e6e00 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs @@ -14,19 +14,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty [JsonProperty("difficulty")] public double Difficulty { get; set; } - [JsonProperty("accuracy")] - public double Accuracy { get; set; } - - [JsonProperty("scaled_score")] - public double ScaledScore { get; set; } - public override IEnumerable GetAttributesForDisplay() { foreach (var attribute in base.GetAttributesForDisplay()) yield return attribute; yield return new PerformanceDisplayAttribute(nameof(Difficulty), "Difficulty", Difficulty); - yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); } } } From 451d4154f02b80f89c14db5f3f101a1beee3a52e Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Sat, 18 Jun 2022 00:45:34 +0800 Subject: [PATCH 017/363] Fix code style --- osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 02c118ae1f..23a95fe866 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty { public class ManiaPerformanceCalculator : PerformanceCalculator { - private int countPerfect; private int countGreat; private int countGood; From 204348f32777fda80630530017cdb1dc9e4935fe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 18 Jun 2022 04:39:53 +0300 Subject: [PATCH 018/363] Fix metronome playing mistimed beat sounds on editor clock resume --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 181f1ecb47..670b7147b3 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -47,6 +47,11 @@ namespace osu.Game.Screens.Edit.Timing public bool EnableClicking { get; set; } = true; + public MetronomeDisplay() + { + AllowMistimedEventFiring = false; + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { From 8ad96fd94baaf489233f88cad238f6044953a452 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 18 Jun 2022 10:55:24 +0900 Subject: [PATCH 019/363] 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 8fcd7ef8c0..c82365d750 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 e9bf000109..d820be4bdc 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 dd344b0cd0..a8c59d676d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 361a87412bbfc9382e22853a2a659a6450dba677 Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Sat, 18 Jun 2022 12:20:47 +0800 Subject: [PATCH 020/363] Update comments --- .../Difficulty/ManiaPerformanceCalculator.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 23a95fe866..ed2654ceda 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -62,13 +62,17 @@ namespace osu.Game.Rulesets.Mania.Difficulty private double computeDifficultyValue(ManiaDifficultyAttributes attributes) { double difficultyValue = Math.Pow(Math.Max(attributes.StarRating - 0.15, 0.05), 2.2) // Star rating to pp curve - * Math.Max(0, 5 * scoreAccuracy - 4) // Accuracy curve - * (1 + 0.1 * Math.Min(1, totalHits / 1500)); // Length bonus + * Math.Max(0, 5 * scoreAccuracy - 4) // From 80% accuracy, 1/20th of total pp is awarded per additional 1% accuracy + * (1 + 0.1 * Math.Min(1, totalHits / 1500)); // Length bonus, capped at 1500 notes return difficultyValue; } private double totalHits => countPerfect + countOk + countGreat + countGood + countMeh + countMiss; + + /// + /// Accuracy used to weight judgements independently from the score's actual accuracy. + /// private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); } } From 984634c5802c61ba0ece44e2597411d9e8fc2772 Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Sat, 18 Jun 2022 22:27:28 +0800 Subject: [PATCH 021/363] Adjust customAccuracy formula slightly --- .../Difficulty/ManiaPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index ed2654ceda..dea02adfbf 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -73,6 +73,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty /// /// Accuracy used to weight judgements independently from the score's actual accuracy. /// - private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); + private double customAccuracy => Math.Max((countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50 - countMiss * 100) / (totalHits * 320), 0); } } From 98d69c846c5e285e5f41984f0465325aec438699 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 19 Jun 2022 17:12:19 +0900 Subject: [PATCH 022/363] Reset spectator speed at last frame --- osu.Game/Screens/Play/SpectatorPlayer.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index d5aec055fc..72a7d3c8a1 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -83,6 +83,18 @@ namespace osu.Game.Screens.Play SetGameplayStartTime(score.Replay.Frames[0].Time); } + protected override void Update() + { + base.Update(); + + if (HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.Value > 1 + && score.Replay.Frames.Count > 0 + && DrawableRuleset.FrameStableClock.CurrentTime >= score.Replay.Frames[^1].Time) + { + HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.Value = 1; + } + } + protected override Score CreateScore(IBeatmap beatmap) => score; protected override ResultsScreen CreateResults(ScoreInfo score) From def87ed7822031164bebbf3ef474286a3533c91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Jun 2022 19:18:29 +0200 Subject: [PATCH 023/363] Add failing test for editor gameplay test using wrong ruleset --- .../Editing/TestSceneEditorNavigation.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs new file mode 100644 index 0000000000..85b50a9b21 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs @@ -0,0 +1,57 @@ +// 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.Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Screens.Edit.GameplayTest; +using osu.Game.Screens.Select; +using osu.Game.Tests.Resources; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorNavigation : OsuGameTestScene + { + [Test] + public void TestEditorGameplayTestAlwaysUsesOriginalRuleset() + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); + + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded); + AddStep("test gameplay", () => + { + var testGameplayButton = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(testGameplayButton); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded); + AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); + + AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield())); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + AddAssert("previous ruleset restored", () => Game.Ruleset.Value.Equals(new ManiaRuleset().RulesetInfo)); + } + } +} From 93b3ede2a05f06a3b37ab9ce72249c1a232e0f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Jun 2022 19:30:34 +0200 Subject: [PATCH 024/363] Always use beatmap ruleset in editor gameplay test mode Fixes cases where opening a convertible beatmap (so any osu! beatmap) with the game-global ruleset being set to anything but osu! would result in opening the editor gameplay test mode with the game-global ruleset rather than the beatmap's. --- osu.Game/Screens/Edit/EditorLoader.cs | 1 + osu.Game/Screens/Select/SongSelect.cs | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 702e22a272..4450367522 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -65,6 +65,7 @@ namespace osu.Game.Screens.Edit base.LoadComplete(); // will be restored via lease, see `DisallowExternalBeatmapRulesetChanges`. + Ruleset.Value = Beatmap.Value.BeatmapInfo.Ruleset; Mods.Value = Array.Empty(); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a17e7823a6..41fb55a856 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -786,7 +786,17 @@ namespace osu.Game.Screens.Select Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue); - decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; + decoupledRuleset.ValueChanged += r => + { + bool wasDisabled = Ruleset.Disabled; + + // a sub-screen may have taken a lease on this decoupled ruleset bindable, + // which would indirectly propagate to the game-global bindable via the `DisabledChanged` callback below. + // to make sure changes sync without crashes, lift the disable for a short while to sync, and then restore the old value. + Ruleset.Disabled = false; + Ruleset.Value = r.NewValue; + Ruleset.Disabled = wasDisabled; + }; decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; Beatmap.BindValueChanged(workingBeatmapChanged); From 3ab8158b923d5ae8581c39d9b67b93a804de65b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Jun 2022 20:08:39 +0200 Subject: [PATCH 025/363] Do not try to set ruleset from beatmap if it's a dummy --- osu.Game/Screens/Edit/EditorLoader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 4450367522..d6af990b52 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -65,7 +65,8 @@ namespace osu.Game.Screens.Edit base.LoadComplete(); // will be restored via lease, see `DisallowExternalBeatmapRulesetChanges`. - Ruleset.Value = Beatmap.Value.BeatmapInfo.Ruleset; + if (!(Beatmap.Value is DummyWorkingBeatmap)) + Ruleset.Value = Beatmap.Value.BeatmapInfo.Ruleset; Mods.Value = Array.Empty(); } From 36df1da57fda5cbfc8c44d97f24c7d4f0d7346f3 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Sun, 19 Jun 2022 14:26:02 -0400 Subject: [PATCH 026/363] Revert "stop component transform when skineditor is hidden" This reverts commit 48ffd6aeb5c131a120a9c82e889348ebf3d21dfb. --- .../Skinning/Editor/SkinSelectionHandler.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 2496d7c657..2e2122f7c2 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -11,7 +11,6 @@ using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; @@ -26,17 +25,8 @@ namespace osu.Game.Skinning.Editor [Resolved] private SkinEditor skinEditor { get; set; } - /// - /// Whether the 's state allows selected blueprints to undergo transformations. - /// Used to prevent operations from being performed on unloaded blueprints. - /// - private bool allowHandling => skinEditor.State.Value == Visibility.Visible; - public override bool HandleRotation(float angle) { - if (!allowHandling) - return false; - if (SelectedBlueprints.Count == 1) { // for single items, rotate around the origin rather than the selection centre. @@ -63,9 +53,6 @@ namespace osu.Game.Skinning.Editor public override bool HandleScale(Vector2 scale, Anchor anchor) { - if (!allowHandling) - return false; - // convert scale to screen space scale = ToScreenSpace(scale) - ToScreenSpace(Vector2.Zero); @@ -143,9 +130,6 @@ namespace osu.Game.Skinning.Editor public override bool HandleFlip(Direction direction, bool flipOverOrigin) { - if (!allowHandling) - return false; - var selectionQuad = getSelectionQuad(); Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1); @@ -166,9 +150,6 @@ namespace osu.Game.Skinning.Editor public override bool HandleMovement(MoveSelectionEvent moveEvent) { - if (!allowHandling) - return false; - foreach (var c in SelectedBlueprints) { var item = c.Item; From a5bc769243f3b0554805fa57855704a8da3b0ac1 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Sun, 19 Jun 2022 14:34:52 -0400 Subject: [PATCH 027/363] clear skineditor selected components on hide --- osu.Game/Skinning/Editor/SkinEditor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index f41cd63f2d..7e9f1d0e8e 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -322,6 +322,12 @@ namespace osu.Game.Skinning.Editor protected override bool OnMouseDown(MouseDownEvent e) => true; + public override void Hide() + { + base.Hide(); + SelectedComponents.Clear(); + } + protected override void PopIn() { this From 2b0e82be4004c8c2ca894f1a6666de9adf754296 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Sun, 19 Jun 2022 14:35:05 -0400 Subject: [PATCH 028/363] add test coverage --- .../TestSceneSkinEditorNavigation.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 78ab7947a7..843d61ca3b 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -88,6 +88,31 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default); } + [Test] + public void TestComponentsDeselectedOnSkinEditorHide() + { + advanceToSongSelect(); + openSkinEditor(); + switchToGameplayScene(); + AddStep("select all components", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.A); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddStep("toggle skin editor", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.S); + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + AddUntilStep("no components selected", () => skinEditor.SelectedComponents.Count == 0); + } + [Test] public void TestAutoplayCompatibleModsRetainedOnEnteringGameplay() { From 26b0815fc862557d4740c79e8fd967643f548a0c Mon Sep 17 00:00:00 2001 From: emu1337 Date: Mon, 20 Jun 2022 04:19:07 +0200 Subject: [PATCH 029/363] fixed casting the wrong skill --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 97899d2db9..fab3fa8425 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; - double speedNotes = (skills[1] as Speed).RelevantNoteCount(); + double speedNotes = (skills[2] as Speed).RelevantNoteCount(); double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; From 5e594e4de0c714ae6855fbf843245c30b40c0ee5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 20 Jun 2022 06:54:15 +0300 Subject: [PATCH 030/363] Work around macOS agent regression affecting iOS CI builds --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef729a779f..c728d89ed1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,6 +126,12 @@ jobs: with: dotnet-version: "6.0.x" + # macOS agents recently have empty NuGet config files, resulting in restore failures, + # see https://github.com/actions/virtual-environments/issues/5768 + # Add the global nuget package source manually for now. + - name: Setup NuGet.Config + run: echo '' > ~/.config/NuGet/NuGet.Config + # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono # cannot accept .sln(f) files as arguments. # Build just the main game for now. From 243806e810de9304e914c9d1c63d1732cafbe15e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 13:29:43 +0900 Subject: [PATCH 031/363] Move common step of toggling skin editor to own method --- .../TestSceneSkinEditorNavigation.cs | 78 +++++++++++-------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 843d61ca3b..7185e5ce49 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -26,29 +26,6 @@ namespace osu.Game.Tests.Visual.Navigation private TestPlaySongSelect songSelect; private SkinEditor skinEditor => Game.ChildrenOfType().FirstOrDefault(); - private void advanceToSongSelect() - { - PushAndConfirm(() => songSelect = new TestPlaySongSelect()); - AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - - AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); - - AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); - } - - private void openSkinEditor() - { - AddStep("open skin editor", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.PressKey(Key.ShiftLeft); - InputManager.Key(Key.S); - InputManager.ReleaseKey(Key.ControlLeft); - InputManager.ReleaseKey(Key.ShiftLeft); - }); - AddUntilStep("skin editor loaded", () => skinEditor != null); - } - [Test] public void TestEditComponentDuringGameplay() { @@ -88,6 +65,24 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default); } + [Test] + public void TestHideSkinEditorWhileDragging() + { + advanceToSongSelect(); + openSkinEditor(); + switchToGameplayScene(); + AddStep("select all components", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.A); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + toggleSkinEditor(); + + AddUntilStep("no components selected", () => skinEditor.SelectedComponents.Count == 0); + } + [Test] public void TestComponentsDeselectedOnSkinEditorHide() { @@ -101,14 +96,7 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.ReleaseKey(Key.ControlLeft); }); - AddStep("toggle skin editor", () => - { - InputManager.PressKey(Key.ControlLeft); - InputManager.PressKey(Key.ShiftLeft); - InputManager.Key(Key.S); - InputManager.ReleaseKey(Key.ControlLeft); - InputManager.ReleaseKey(Key.ShiftLeft); - }); + toggleSkinEditor(); AddUntilStep("no components selected", () => skinEditor.SelectedComponents.Count == 0); } @@ -171,6 +159,34 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("mod overlay closed", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); } + private void advanceToSongSelect() + { + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + } + + private void openSkinEditor() + { + toggleSkinEditor(); + AddUntilStep("skin editor loaded", () => skinEditor != null); + } + + private void toggleSkinEditor() + { + AddStep("toggle skin editor", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.S); + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + } + private void switchToGameplayScene() { AddStep("Click gameplay scene button", () => skinEditor.ChildrenOfType().First(b => b.Text == "Gameplay").TriggerClick()); From 329f1a08222c6ac9017025f484c771bc1c45d7aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 13:45:50 +0900 Subject: [PATCH 032/363] Add test coverage of original fail case and improve test reliability and code quality --- .../TestSceneSkinEditorNavigation.cs | 47 +++++++++++++++---- .../Skinning/Editor/SkinComponentToolbox.cs | 2 +- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 7185e5ce49..0c99cf15eb 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -8,7 +8,9 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -16,6 +18,7 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning.Editor; using osu.Game.Tests.Beatmaps.IO; +using osuTK; using osuTK.Input; using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation; @@ -66,11 +69,14 @@ namespace osu.Game.Tests.Visual.Navigation } [Test] - public void TestHideSkinEditorWhileDragging() + public void TestComponentsDeselectedOnSkinEditorHide() { advanceToSongSelect(); openSkinEditor(); switchToGameplayScene(); + + AddUntilStep("wait for components", () => skinEditor.ChildrenOfType().Any()); + AddStep("select all components", () => { InputManager.PressKey(Key.ControlLeft); @@ -78,27 +84,50 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.ReleaseKey(Key.ControlLeft); }); + AddUntilStep("components selected", () => skinEditor.SelectedComponents.Count > 0); + toggleSkinEditor(); AddUntilStep("no components selected", () => skinEditor.SelectedComponents.Count == 0); } [Test] - public void TestComponentsDeselectedOnSkinEditorHide() + public void TestSwitchScreenWhileDraggingComponent() { + Vector2 firstBlueprintCentre = Vector2.Zero; + ScheduledDelegate movementDelegate = null; + advanceToSongSelect(); + openSkinEditor(); - switchToGameplayScene(); - AddStep("select all components", () => + + AddStep("add skinnable component", () => { - InputManager.PressKey(Key.ControlLeft); - InputManager.Key(Key.A); - InputManager.ReleaseKey(Key.ControlLeft); + skinEditor.ChildrenOfType().First().TriggerClick(); }); - toggleSkinEditor(); + AddUntilStep("newly added component selected", () => skinEditor.SelectedComponents.Count == 1); - AddUntilStep("no components selected", () => skinEditor.SelectedComponents.Count == 0); + AddStep("start drag", () => + { + firstBlueprintCentre = skinEditor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre; + + InputManager.MoveMouseTo(firstBlueprintCentre); + InputManager.PressButton(MouseButton.Left); + }); + + AddStep("start movement", () => movementDelegate = Scheduler.AddDelayed(() => { InputManager.MoveMouseTo(firstBlueprintCentre += new Vector2(1)); }, 10, true)); + + toggleSkinEditor(); + AddStep("exit song select", () => songSelect.Exit()); + + AddUntilStep("wait for blueprints removed", () => !skinEditor.ChildrenOfType().Any()); + + AddStep("stop drag", () => + { + InputManager.ReleaseButton(MouseButton.Left); + movementDelegate?.Cancel(); + }); } [Test] diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 7de9cd5108..344a659627 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -88,7 +88,7 @@ namespace osu.Game.Skinning.Editor } } - private class ToolboxComponentButton : OsuButton + public class ToolboxComponentButton : OsuButton { protected override bool ShouldBeConsideredForInput(Drawable child) => false; From e4d0c7a0fb179ec5f8c56e1885811de7ec8525ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 13:59:20 +0900 Subject: [PATCH 033/363] Move beatmap import step to only be required when entering gameplay --- .../Visual/Navigation/TestSceneSkinEditorNavigation.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 0c99cf15eb..cc76e530c8 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -192,10 +192,6 @@ namespace osu.Game.Tests.Visual.Navigation { PushAndConfirm(() => songSelect = new TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); - - AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); - - AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); } private void openSkinEditor() @@ -218,6 +214,9 @@ namespace osu.Game.Tests.Visual.Navigation private void switchToGameplayScene() { + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + AddStep("Click gameplay scene button", () => skinEditor.ChildrenOfType().First(b => b.Text == "Gameplay").TriggerClick()); AddUntilStep("wait for player", () => From a6c8a832aaf9d9fd4cda147eef6850d1daa8ff37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:14:57 +0900 Subject: [PATCH 034/363] Remove `Import(ArchiveReader)` and redirect existing usages to `Import(ImportTask)` --- .../Database/BeatmapImporterTests.cs | 27 +++------- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 53 +++++++++---------- .../Skins/TestSceneBeatmapSkinResources.cs | 4 +- .../Skins/TestSceneSkinResources.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 3 -- .../Database/RealmArchiveModelImporter.cs | 4 +- osu.Game/Screens/Menu/IntroScreen.cs | 3 +- osu.Game/Skinning/SkinManager.cs | 4 -- 8 files changed, 39 insertions(+), 63 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index f9cd75d8c3..841622f0db 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -15,7 +15,6 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Extensions; -using osu.Game.IO.Archives; using osu.Game.Models; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; @@ -39,10 +38,7 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapImporter(storage, realm)) using (new RealmRulesetStore(realm, storage)) { - Live? beatmapSet; - - using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) - beatmapSet = await importer.Import(reader); + var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); Assert.NotNull(beatmapSet); Debug.Assert(beatmapSet != null); @@ -83,10 +79,7 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapImporter(storage, realm)) using (new RealmRulesetStore(realm, storage)) { - Live? beatmapSet; - - using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) - beatmapSet = await importer.Import(reader); + var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); Assert.NotNull(beatmapSet); Debug.Assert(beatmapSet != null); @@ -146,11 +139,8 @@ namespace osu.Game.Tests.Database { Task.Run(async () => { - Live? beatmapSet; - - using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) - // ReSharper disable once AccessToDisposedClosure - beatmapSet = await importer.Import(reader); + // ReSharper disable once AccessToDisposedClosure + var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); Assert.NotNull(beatmapSet); Debug.Assert(beatmapSet != null); @@ -173,13 +163,8 @@ namespace osu.Game.Tests.Database using (var importer = new BeatmapImporter(storage, realm)) using (new RealmRulesetStore(realm, storage)) { - Live? imported; - - using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) - { - imported = await importer.Import(reader); - EnsureLoaded(realm.Realm); - } + var imported = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); + EnsureLoaded(realm.Realm); Assert.AreEqual(1, realm.Realm.All().Count()); diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 4db88cbe82..f3adb85006 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -15,7 +15,6 @@ using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; -using osu.Game.IO.Archives; using osu.Game.Skinning; using SharpCompress.Archives.Zip; @@ -28,7 +27,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSingleImportDifferentFilename() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); // When the import filename doesn't match, it should be appended (and update the skin.ini). assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu); @@ -37,7 +36,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSingleImportWeirdIniFileCase() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", iniFilename: "Skin.InI"), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner", iniFilename: "Skin.InI"), "skin.osk")); // When the import filename doesn't match, it should be appended (and update the skin.ini). assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu); @@ -46,7 +45,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSingleImportMissingSectionHeader() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk")); // When the import filename doesn't match, it should be appended (and update the skin.ini). assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu); @@ -55,7 +54,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSingleImportMatchingFilename() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "test skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "test skin.osk")); // When the import filename matches it shouldn't be appended. assertCorrectMetadata(import1, "test skin", "skinner", osu); @@ -64,7 +63,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSingleImportNoIniFile() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithNonIniFile(), "test skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithNonIniFile(), "test skin.osk")); // When the import filename matches it shouldn't be appended. assertCorrectMetadata(import1, "test skin", "Unknown", osu); @@ -73,7 +72,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestEmptyImportImportsWithFilename() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createEmptyOsk(), "test skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createEmptyOsk(), "test skin.osk")); // When the import filename matches it shouldn't be appended. assertCorrectMetadata(import1, "test skin", "Unknown", osu); @@ -86,8 +85,8 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestImportTwiceWithSameMetadataAndFilename() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); assertImportedOnce(import1, import2); }); @@ -96,8 +95,8 @@ namespace osu.Game.Tests.Skins.IO public Task TestImportTwiceWithNoMetadataSameDownloadFilename() => runSkinTest(async osu => { // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk")); assertImportedOnce(import1, import2); }); @@ -105,10 +104,10 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestImportUpperCasedOskArchive() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "name 1.OsK")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "name 1.OsK")); assertCorrectMetadata(import1, "name 1", "author 1", osu); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "name 1.oSK")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "name 1.oSK")); assertImportedOnce(import1, import2); }); @@ -118,7 +117,7 @@ namespace osu.Game.Tests.Skins.IO { MemoryStream exportStream = new MemoryStream(); - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "custom.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "custom.osk")); assertCorrectMetadata(import1, "name 1 [custom]", "author 1", osu); import1.PerformRead(s => @@ -128,7 +127,7 @@ namespace osu.Game.Tests.Skins.IO string exportFilename = import1.GetDisplayString(); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(exportStream, $"{exportFilename}.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(exportStream, $"{exportFilename}.osk")); assertCorrectMetadata(import2, "name 1 [custom]", "author 1", osu); assertImportedOnce(import1, import2); @@ -137,8 +136,8 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSameMetadataNameSameFolderName() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1")); assertImportedOnce(import1, import2); assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu); @@ -151,8 +150,8 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestImportTwiceWithSameMetadataButDifferentFilename() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin2.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin2.osk")); assertImportedBoth(import1, import2); }); @@ -161,8 +160,8 @@ namespace osu.Game.Tests.Skins.IO public Task TestImportTwiceWithNoMetadataDifferentDownloadFilename() => runSkinTest(async osu => { // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download2.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download2.osk")); assertImportedBoth(import1, import2); }); @@ -170,8 +169,8 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestImportTwiceWithSameFilenameDifferentMetadata() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin v2", "skinner"), "skin.osk")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin v2.1", "skinner"), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin v2", "skinner"), "skin.osk")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin v2.1", "skinner"), "skin.osk")); assertImportedBoth(import1, import2); assertCorrectMetadata(import1, "test skin v2 [skin]", "skinner", osu); @@ -181,8 +180,8 @@ namespace osu.Game.Tests.Skins.IO [Test] public Task TestSameMetadataNameDifferentFolderName() => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1")); - var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 2")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1")); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 2")); assertImportedBoth(import1, import2); assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu); @@ -358,10 +357,10 @@ namespace osu.Game.Tests.Skins.IO } } - private async Task> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) + private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import = null) { var skinManager = osu.Dependencies.Get(); - return await skinManager.Import(archive); + return await skinManager.Import(import); } } } diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index bd7e1f8ec5..f4cea2c8cc 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -10,7 +10,7 @@ using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.IO.Archives; +using osu.Game.Database; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Skins [BackgroundDependencyLoader] private void load() { - var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).GetResultSafely(); + var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-beatmap.osz"), "ogg-beatmap.osz")).GetResultSafely(); imported?.PerformRead(s => { diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index 97588f4053..42c1eeb6d1 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -8,7 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Audio; -using osu.Game.IO.Archives; +using osu.Game.Database; using osu.Game.Skinning; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Skins [BackgroundDependencyLoader] private void load() { - var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).GetResultSafely(); + var imported = skins.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-skin.osk"), "ogg-skin.osk")).GetResultSafely(); skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1dd3f82426..91e1744258 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -409,9 +409,6 @@ namespace osu.Game.Beatmaps public Task?> Import(ImportTask task, bool batchImport = false, CancellationToken cancellationToken = default) => beatmapImporter.Import(task, batchImport, cancellationToken); - public Task?> Import(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) => - beatmapImporter.Import(archive, batchImport, cancellationToken); - public Live? Import(BeatmapSetInfo item, ArchiveReader? archive = null, CancellationToken cancellationToken = default) => beatmapImporter.Import(item, archive, false, cancellationToken); diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index c5e04a8a0f..84decdb3ef 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -188,7 +188,7 @@ namespace osu.Game.Database Live? import; using (ArchiveReader reader = task.GetReader()) - import = await Import(reader, batchImport, cancellationToken).ConfigureAwait(false); + import = await importFromArchive(reader, batchImport, cancellationToken).ConfigureAwait(false); // We may or may not want to delete the file depending on where it is stored. // e.g. reconstructing/repairing database with items from default storage. @@ -213,7 +213,7 @@ namespace osu.Game.Database /// The archive to be imported. /// Whether this import is part of a larger batch. /// An optional cancellation token. - public async Task?> Import(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) + private async Task?> importFromArchive(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 766b5bf2a4..c81195bbd3 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -19,7 +19,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -141,7 +140,7 @@ namespace osu.Game.Screens.Menu { // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // this could happen if a user has nuked their files store. for now, reimport to repair this. - var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); + var import = beatmaps.Import(new ImportTask(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely(); import?.PerformWrite(b => b.Protected = true); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index be6528db3f..16a0d84263 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -24,7 +24,6 @@ using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO; -using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; using osu.Game.Utils; @@ -275,9 +274,6 @@ namespace osu.Game.Skinning public Task> Import(ImportTask task, bool batchImport = false, CancellationToken cancellationToken = default) => skinImporter.Import(task, batchImport, cancellationToken); - public Task> Import(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) => - skinImporter.Import(archive, batchImport, cancellationToken); - #endregion public void Delete([CanBeNull] Expression> filter = null, bool silent = false) From 92f1a2958c1645dff914fc3f2b0e2a59d803da77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:18:07 +0900 Subject: [PATCH 035/363] Rename `Import(TModel)` to `ImportModel` to differentiate from other import methods --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 2 +- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Database/RealmArchiveModelImporter.cs | 4 ++-- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 841622f0db..d8856a2ca7 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -745,7 +745,7 @@ namespace osu.Game.Tests.Database } }; - var imported = importer.Import(toImport); + var imported = importer.ImportModel(toImport); realm.Run(r => r.Refresh()); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index e502a71f34..1d33a895eb 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -227,10 +227,10 @@ namespace osu.Game.Tests.Online this.testBeatmapManager = testBeatmapManager; } - public override Live Import(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) + public override Live ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) { testBeatmapManager.AllowImport.Task.WaitSafely(); - return (testBeatmapManager.CurrentImport = base.Import(item, archive, batchImport, cancellationToken)); + return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken)); } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 91e1744258..06a8ad5188 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -101,7 +101,7 @@ namespace osu.Game.Beatmaps foreach (BeatmapInfo b in beatmapSet.Beatmaps) b.BeatmapSet = beatmapSet; - var imported = beatmapImporter.Import(beatmapSet); + var imported = beatmapImporter.ImportModel(beatmapSet); if (imported == null) throw new InvalidOperationException("Failed to import new beatmap"); @@ -410,7 +410,7 @@ namespace osu.Game.Beatmaps beatmapImporter.Import(task, batchImport, cancellationToken); public Live? Import(BeatmapSetInfo item, ArchiveReader? archive = null, CancellationToken cancellationToken = default) => - beatmapImporter.Import(item, archive, false, cancellationToken); + beatmapImporter.ImportModel(item, archive, false, cancellationToken); public IEnumerable HandledExtensions => beatmapImporter.HandledExtensions; diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 84decdb3ef..3d61345f34 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -236,7 +236,7 @@ namespace osu.Game.Database return null; } - var scheduledImport = Task.Factory.StartNew(() => Import(model, archive, batchImport, cancellationToken), + var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken), cancellationToken, TaskCreationOptions.HideScheduler, batchImport ? import_scheduler_batch : import_scheduler); @@ -251,7 +251,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// If true, imports will be skipped before they begin, given an existing model matches on hash and filenames. Should generally only be used for large batch imports, as it may defy user expectations when updating an existing model. /// An optional cancellation token. - public virtual Live? Import(TModel item, ArchiveReader? archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => Realm.Run(realm => + public virtual Live? ImportModel(TModel item, ArchiveReader? archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => Realm.Run(realm => { cancellationToken.ThrowIfCancellationRequested(); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7ccf7a58b8..44aafbec74 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -265,7 +265,7 @@ namespace osu.Game.Scoring public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) => scoreImporter.Import(notification, tasks); public Live Import(ScoreInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => - scoreImporter.Import(item, archive, batchImport, cancellationToken); + scoreImporter.ImportModel(item, archive, batchImport, cancellationToken); #region Implementation of IPresentImports diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 16a0d84263..38eff2f0d6 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -167,7 +167,7 @@ namespace osu.Game.Skinning Name = NamingUtils.GetNextBestName(existingSkinNames, $@"{s.Name} (modified)") }; - var result = skinImporter.Import(skinInfo); + var result = skinImporter.ImportModel(skinInfo); if (result != null) { From dd93c7359e184f1bf630dacfd9dcfaf1b6f6ff7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:25:43 +0900 Subject: [PATCH 036/363] Update xmldoc for `importFromArchive` method --- .../Database/RealmArchiveModelImporter.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 3d61345f34..f3174b46b8 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -208,8 +208,11 @@ namespace osu.Game.Database } /// - /// Silently import an item from an . + /// Create and import a model based off the provided . /// + /// + /// This method also handled queueing the import task on a relevant import thread pool. + /// /// The archive to be imported. /// Whether this import is part of a larger batch. /// An optional cancellation token. @@ -225,6 +228,13 @@ namespace osu.Game.Database if (model == null) return null; + + var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken), + cancellationToken, + TaskCreationOptions.HideScheduler, + batchImport ? import_scheduler_batch : import_scheduler); + + return await scheduledImport.ConfigureAwait(false); } catch (TaskCanceledException) { @@ -235,13 +245,6 @@ namespace osu.Game.Database LogForModel(model, @$"Model creation of {archive.Name} failed.", e); return null; } - - var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken), - cancellationToken, - TaskCreationOptions.HideScheduler, - batchImport ? import_scheduler_batch : import_scheduler); - - return await scheduledImport.ConfigureAwait(false); } /// From 03ab6fc141ea1931bf877ca55fd5aeac4162cf3a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 20 Jun 2022 14:56:04 +0900 Subject: [PATCH 037/363] Implement IEquatable on ControlPoint --- .../Beatmaps/ControlPoints/ControlPoint.cs | 15 ++++++++-- .../ControlPoints/ControlPointGroup.cs | 26 +++++++++++++---- .../ControlPoints/DifficultyControlPoint.cs | 15 ++++++++-- .../ControlPoints/EffectControlPoint.cs | 17 +++++++++-- .../ControlPoints/SampleControlPoint.cs | 16 +++++++++-- .../ControlPoints/TimingControlPoint.cs | 14 +++++++++- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 28 ++++++++++++++++--- 7 files changed, 109 insertions(+), 22 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 5f06e03509..fd06c898ab 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Newtonsoft.Json; using osu.Game.Graphics; @@ -11,7 +9,7 @@ using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { - public abstract class ControlPoint : IComparable, IDeepCloneable + public abstract class ControlPoint : IComparable, IDeepCloneable, IEquatable { /// /// The time at which the control point takes effect. @@ -48,5 +46,16 @@ namespace osu.Game.Beatmaps.ControlPoints { Time = other.Time; } + + public sealed override bool Equals(object? obj) + => obj is ControlPoint otherControlPoint + && Equals(otherControlPoint); + + public virtual bool Equals(ControlPoint? other) + => other != null + && Time == other.Time; + + // ReSharper disable once NonReadonlyMemberInGetHashCode + public override int GetHashCode() => Time.GetHashCode(); } } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs index 9df38b01ee..db479f0e5b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -1,18 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Bindables; namespace osu.Game.Beatmaps.ControlPoints { - public class ControlPointGroup : IComparable + public class ControlPointGroup : IComparable, IEquatable { - public event Action ItemAdded; - public event Action ItemRemoved; + public event Action? ItemAdded; + public event Action? ItemRemoved; /// /// The time at which the control point takes effect. @@ -48,5 +46,23 @@ namespace osu.Game.Beatmaps.ControlPoints controlPoints.Remove(point); ItemRemoved?.Invoke(point); } + + public sealed override bool Equals(object? obj) + => obj is ControlPointGroup otherGroup + && Equals(otherGroup); + + public virtual bool Equals(ControlPointGroup? other) + => other != null + && Time == other.Time + && ControlPoints.SequenceEqual(other.ControlPoints); + + public override int GetHashCode() + { + HashCode hashCode = new HashCode(); + hashCode.Add(Time); + foreach (var point in controlPoints) + hashCode.Add(point); + return hashCode.ToHashCode(); + } } } diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 8b3d755fb6..6f624d20c9 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; using osu.Framework.Bindables; using osu.Game.Graphics; using osuTK.Graphics; @@ -12,7 +11,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// Note that going forward, this control point type should always be assigned directly to HitObjects. /// - public class DifficultyControlPoint : ControlPoint + public class DifficultyControlPoint : ControlPoint, IEquatable { public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint { @@ -51,5 +50,15 @@ namespace osu.Game.Beatmaps.ControlPoints base.CopyFrom(other); } + + public override bool Equals(ControlPoint? other) + => other is DifficultyControlPoint otherDifficultyControlPoint + && Equals(otherDifficultyControlPoint); + + public bool Equals(DifficultyControlPoint? other) + => base.Equals(other) + && SliderVelocity == other.SliderVelocity; + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SliderVelocity); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 35425972da..365d024035 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,15 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; using osu.Framework.Bindables; using osu.Game.Graphics; using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { - public class EffectControlPoint : ControlPoint + public class EffectControlPoint : ControlPoint, IEquatable { public static readonly EffectControlPoint DEFAULT = new EffectControlPoint { @@ -83,5 +82,17 @@ namespace osu.Game.Beatmaps.ControlPoints base.CopyFrom(other); } + + public override bool Equals(ControlPoint? other) + => other is EffectControlPoint otherEffectControlPoint + && Equals(otherEffectControlPoint); + + public bool Equals(EffectControlPoint? other) + => base.Equals(other) + && OmitFirstBarLine == other.OmitFirstBarLine + && ScrollSpeed == other.ScrollSpeed + && KiaiMode == other.KiaiMode; + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), OmitFirstBarLine, ScrollSpeed, KiaiMode); } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index bed499ef3f..ab575dfeda 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; using osu.Framework.Bindables; using osu.Game.Audio; using osu.Game.Graphics; @@ -13,7 +12,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// Note that going forward, this control point type should always be assigned directly to HitObjects. /// - public class SampleControlPoint : ControlPoint + public class SampleControlPoint : ControlPoint, IEquatable { public const string DEFAULT_BANK = "normal"; @@ -85,5 +84,16 @@ namespace osu.Game.Beatmaps.ControlPoints base.CopyFrom(other); } + + public override bool Equals(ControlPoint? other) + => other is SampleControlPoint otherSampleControlPoint + && Equals(otherSampleControlPoint); + + public bool Equals(SampleControlPoint? other) + => base.Equals(other) + && SampleBank == other.SampleBank + && SampleVolume == other.SampleVolume; + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SampleBank, SampleVolume); } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 922439fcb8..87ef4e86d5 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.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; using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; @@ -8,7 +9,7 @@ using osuTK.Graphics; namespace osu.Game.Beatmaps.ControlPoints { - public class TimingControlPoint : ControlPoint + public class TimingControlPoint : ControlPoint, IEquatable { /// /// The time signature at this control point. @@ -77,5 +78,16 @@ namespace osu.Game.Beatmaps.ControlPoints base.CopyFrom(other); } + + public override bool Equals(ControlPoint? other) + => other is TimingControlPoint otherTimingControlPoint + && Equals(otherTimingControlPoint); + + public bool Equals(TimingControlPoint? other) + => base.Equals(other) + && TimeSignature.Equals(other.TimeSignature) + && BeatLength.Equals(other.BeatLength); + + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), TimeSignature, BeatLength); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 0865561854..cc6a9126bd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Extensions; @@ -162,7 +160,7 @@ namespace osu.Game.Beatmaps.Formats } [Obsolete("Do not use unless you're a legacy ruleset and 100% sure.")] - public class LegacyDifficultyControlPoint : DifficultyControlPoint + public class LegacyDifficultyControlPoint : DifficultyControlPoint, IEquatable { /// /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. @@ -188,9 +186,20 @@ namespace osu.Game.Beatmaps.Formats BpmMultiplier = ((LegacyDifficultyControlPoint)other).BpmMultiplier; } + + public override bool Equals(ControlPoint? other) + => other is LegacyDifficultyControlPoint otherLegacyDifficultyControlPoint + && Equals(otherLegacyDifficultyControlPoint); + + public bool Equals(LegacyDifficultyControlPoint? other) + => base.Equals(other) + && BpmMultiplier == other.BpmMultiplier; + + // ReSharper disable once NonReadonlyMemberInGetHashCode + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), BpmMultiplier); } - internal class LegacySampleControlPoint : SampleControlPoint + internal class LegacySampleControlPoint : SampleControlPoint, IEquatable { public int CustomSampleBank; @@ -215,6 +224,17 @@ namespace osu.Game.Beatmaps.Formats CustomSampleBank = ((LegacySampleControlPoint)other).CustomSampleBank; } + + public override bool Equals(ControlPoint? other) + => other is LegacySampleControlPoint otherLegacySampleControlPoint + && Equals(otherLegacySampleControlPoint); + + public bool Equals(LegacySampleControlPoint? other) + => base.Equals(other) + && CustomSampleBank == other.CustomSampleBank; + + // ReSharper disable once NonReadonlyMemberInGetHashCode + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank); } } } From a922ea9b0163a805c5b2108ddecd22cb6d412c4a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 20 Jun 2022 15:28:36 +0900 Subject: [PATCH 038/363] Fix selection by directly comparing control points Previously, all control points would get replaced, which led to performance issues that was worked around in this PR. By comparing control points, we're able to get good performance without requiring the workaround. --- .../Edit/LegacyEditorBeatmapPatcher.cs | 28 +++++++++++++++---- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 8 ++---- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 6b18855396..01bc01bf5b 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -6,12 +6,14 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using DiffPlex; using DiffPlex.Model; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Skinning; @@ -50,14 +52,28 @@ namespace osu.Game.Screens.Edit if (removedIndices.Count == 0 && addedIndices.Count == 0) return; - // Due to conversion from legacy to non-legacy control points, it becomes difficult to diff control points correctly. - // So instead _all_ control points are reloaded if _any_ control point is changed. + ControlPointInfo newControlPoints = EditorBeatmap.ConvertControlPoints(getNewBeatmap().ControlPointInfo); - var newControlPoints = EditorBeatmap.ConvertControlPoints(getNewBeatmap().ControlPointInfo); + // Remove all groups from the current beatmap which don't have a corresponding equal group in the new beatmap. + foreach (var oldGroup in editorBeatmap.ControlPointInfo.Groups.ToArray()) + { + var newGroup = newControlPoints.GroupAt(oldGroup.Time); - editorBeatmap.ControlPointInfo.Clear(); - foreach (var point in newControlPoints.AllControlPoints) - editorBeatmap.ControlPointInfo.Add(point.Time, point); + if (!oldGroup.Equals(newGroup)) + editorBeatmap.ControlPointInfo.RemoveGroup(oldGroup); + } + + // Add all groups from the new beatmap which don't have a corresponding equal group in the old beatmap. + foreach (var newGroup in newControlPoints.Groups) + { + var oldGroup = editorBeatmap.ControlPointInfo.GroupAt(newGroup.Time); + + if (!newGroup.Equals(oldGroup)) + { + foreach (var point in newGroup.ControlPoints) + editorBeatmap.ControlPointInfo.Add(newGroup.Time, point); + } + } } private void processHitObjects(DiffResult result, Func getNewBeatmap) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 27b62f1104..60cb263a79 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -139,12 +139,8 @@ namespace osu.Game.Screens.Edit.Timing controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => { - // This AddOnce() works around performance issues from the LegacyEditorBeatmapPatcher re-initialising all control points every undo & redo. - Scheduler.AddOnce(() => - { - table.ControlGroups = controlPointGroups; - changeHandler?.SaveState(); - }); + table.ControlGroups = controlPointGroups; + changeHandler?.SaveState(); }, true); } From 678b18dde68ab4d9ba874edfd6de73bc2b28c2e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:29:56 +0900 Subject: [PATCH 039/363] Ensure any non-`MemoryStream` streams are closed as part of `ImportTask` --- osu.Game/Database/ImportTask.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs index 0643300fc5..17ca691be3 100644 --- a/osu.Game/Database/ImportTask.cs +++ b/osu.Game/Database/ImportTask.cs @@ -62,6 +62,7 @@ namespace osu.Game.Database { // This isn't used in any current path. May need to reconsider for performance reasons (ie. if we don't expect the incoming stream to be copied out). memoryStream = new MemoryStream(stream.ReadAllBytesToArray()); + stream.Dispose(); } if (ZipUtils.IsZipArchive(memoryStream)) From 882e1c69e50bd53fe87df0525d3f5122517f10dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:36:44 +0900 Subject: [PATCH 040/363] Move nested task back outside try-catch to avoid double-error --- osu.Game/Database/RealmArchiveModelImporter.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index f3174b46b8..e6956d3184 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -228,13 +228,6 @@ namespace osu.Game.Database if (model == null) return null; - - var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken), - cancellationToken, - TaskCreationOptions.HideScheduler, - batchImport ? import_scheduler_batch : import_scheduler); - - return await scheduledImport.ConfigureAwait(false); } catch (TaskCanceledException) { @@ -245,6 +238,13 @@ namespace osu.Game.Database LogForModel(model, @$"Model creation of {archive.Name} failed.", e); return null; } + + var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken), + cancellationToken, + TaskCreationOptions.HideScheduler, + batchImport ? import_scheduler_batch : import_scheduler); + + return await scheduledImport.ConfigureAwait(false); } /// From 4aa32038c3572fb648694a1f16302dea510a365e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 15:38:44 +0900 Subject: [PATCH 041/363] Add note about stream provided to `ImportTask` being disposed implicitly --- osu.Game/Database/ImportTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs index 17ca691be3..e7f599d85f 100644 --- a/osu.Game/Database/ImportTask.cs +++ b/osu.Game/Database/ImportTask.cs @@ -33,7 +33,7 @@ namespace osu.Game.Database } /// - /// Construct a new import task from a stream. + /// Construct a new import task from a stream. The provided stream will be disposed after reading. /// public ImportTask(Stream stream, string filename) { From e82d948acc5d9ead0202f78707ff12d5069d08de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 16:06:22 +0900 Subject: [PATCH 042/363] Add test coverage of import throwing when no valid `.osu` files are found --- .../Database/BeatmapImporterTests.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index f9cd75d8c3..b018a26aec 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -291,6 +291,42 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestImportDirectoryWithEmptyOsuFiles() + { + RunTestWithRealmAsync(async (realm, storage) => + { + using var importer = new BeatmapImporter(storage, realm); + using var store = new RealmRulesetStore(realm, storage); + + string? temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + foreach (var file in new DirectoryInfo(extractedFolder).GetFiles("*.osu")) + { + using (file.Open(FileMode.Create)) + { + // empty file. + } + } + + var imported = await importer.Import(new ImportTask(extractedFolder)); + Assert.IsNull(imported); + } + finally + { + Directory.Delete(extractedFolder, true); + } + }); + } + [Test] public void TestImportThenImportWithReZip() { From d38defada461430817c8e99d21b49840ef769eb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 16:07:10 +0900 Subject: [PATCH 043/363] Silence exception and provide more log output when import fails due to empty `.osu` files --- osu.Game/Beatmaps/BeatmapImporter.cs | 9 +++++++++ osu.Game/Beatmaps/Formats/Decoder.cs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index e463492e2b..237088036c 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -177,8 +177,17 @@ namespace osu.Game.Beatmaps } Beatmap beatmap; + using (var stream = new LineBufferedReader(reader.GetStream(mapName))) + { + if (stream.PeekLine() == null) + { + Logger.Log($"No content found in first .osu file of beatmap archive ({reader.Name} / {mapName})", LoggingTarget.Database); + return null; + } + beatmap = Decoder.GetDecoder(stream).Decode(stream); + } return new BeatmapSetInfo { diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index aad29f46fb..ca1bcc97fd 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -74,7 +74,7 @@ namespace osu.Game.Beatmaps.Formats } if (line == null) - throw new IOException("Unknown file format (null)"); + throw new IOException("Unknown file format (no content)"); var decoder = typedDecoders.Where(d => line.StartsWith(d.Key, StringComparison.InvariantCulture)).Select(d => d.Value).FirstOrDefault(); From ca287d093621d3997e1f8abb1b1b60e33a026e94 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 20 Jun 2022 16:38:11 +0900 Subject: [PATCH 044/363] Fix group deselected when table is recreated --- osu.Game/Screens/Edit/EditorTable.cs | 5 ++--- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index 8765dae384..a290cce708 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Edit [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { - hoveredBackground.Colour = colourHover = colours.Background1; + colourHover = colours.Background1; colourSelected = colours.Colour3; } @@ -105,8 +105,7 @@ namespace osu.Game.Screens.Edit { base.LoadComplete(); - // Reduce flicker of rows when offset is being changed rapidly. - // Probably need to reconsider this. + updateState(); FinishTransforms(true); } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 88c738c1d2..6d46b429f0 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -44,6 +44,7 @@ namespace osu.Game.Screens.Edit.Timing { BackgroundFlow.Add(new RowBackground(group) { + Selected = group.Equals(selectedGroup?.Value), Action = () => { selectedGroup.Value = group; From 16281f4a4831cebda36a6bf669ed59ab99408b40 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 20 Jun 2022 16:52:01 +0900 Subject: [PATCH 045/363] Properly annotate method to allow null --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 2 +- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index fd06c898ab..b91d609849 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -28,7 +28,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// An existing control point to compare with. /// Whether this is redundant when placed alongside . - public abstract bool IsRedundant(ControlPoint existing); + public abstract bool IsRedundant(ControlPoint? existing); /// /// Create an unbound copy of this control point. diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 6f624d20c9..c199d1da59 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps.ControlPoints set => SliderVelocityBindable.Value = value; } - public override bool IsRedundant(ControlPoint existing) + public override bool IsRedundant(ControlPoint? existing) => existing is DifficultyControlPoint existingDifficulty && SliderVelocity == existingDifficulty.SliderVelocity; diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 365d024035..ead07b4eaa 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -67,7 +67,7 @@ namespace osu.Game.Beatmaps.ControlPoints set => KiaiModeBindable.Value = value; } - public override bool IsRedundant(ControlPoint existing) + public override bool IsRedundant(ControlPoint? existing) => !OmitFirstBarLine && existing is EffectControlPoint existingEffect && KiaiMode == existingEffect.KiaiMode diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index ab575dfeda..78dec67937 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -72,7 +72,7 @@ namespace osu.Game.Beatmaps.ControlPoints public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo) => hitSampleInfo.With(newBank: hitSampleInfo.Bank ?? SampleBank, newVolume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume); - public override bool IsRedundant(ControlPoint existing) + public override bool IsRedundant(ControlPoint? existing) => existing is SampleControlPoint existingSample && SampleBank == existingSample.SampleBank && SampleVolume == existingSample.SampleVolume; diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 87ef4e86d5..23d4d10fd8 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.ControlPoints public double BPM => 60000 / BeatLength; // Timing points are never redundant as they can change the time signature. - public override bool IsRedundant(ControlPoint existing) => false; + public override bool IsRedundant(ControlPoint? existing) => false; public override void CopyFrom(ControlPoint other) { diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index cc6a9126bd..a5e6ac0a1c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -213,7 +213,7 @@ namespace osu.Game.Beatmaps.Formats return baseInfo; } - public override bool IsRedundant(ControlPoint existing) + public override bool IsRedundant(ControlPoint? existing) => base.IsRedundant(existing) && existing is LegacySampleControlPoint existingSample && CustomSampleBank == existingSample.CustomSampleBank; From e0c82d11ab339009718b880c95697ef74ede594c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 20 Jun 2022 16:53:03 +0900 Subject: [PATCH 046/363] Convert == usages to ReferenceEquals --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 8 ++++---- .../Visual/UserInterface/TestSceneBeatSyncedContainer.cs | 4 ++-- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 4 ++-- .../Timelines/Summary/Parts/ControlPointPart.cs | 2 +- .../Components/Timeline/TimelineControlPointDisplay.cs | 2 +- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 6 +++--- osu.Game/Screens/Edit/EditorClock.cs | 2 +- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 3 ++- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs | 2 +- 12 files changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 4589a8eca1..f03bcffdc8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -374,7 +374,7 @@ namespace osu.Game.Rulesets.Osu.Mods int i = 0; double currentTime = timingPoint.Time; - while (!definitelyBigger(currentTime, mapEndTime) && controlPointInfo.TimingPointAt(currentTime) == timingPoint) + while (!definitelyBigger(currentTime, mapEndTime) && ReferenceEquals(controlPointInfo.TimingPointAt(currentTime), timingPoint)) { beats.Add(Math.Floor(currentTime)); i++; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index cf1246ef07..e9bbf1e33d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -117,8 +117,8 @@ namespace osu.Game.Tests.Visual.Editing // After placement these must be non-default as defaults are read-only. AddAssert("Placed object has non-default control points", () => - EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && - EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); + !ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) && + !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT)); ReloadEditorToSameBeatmap(); @@ -126,8 +126,8 @@ namespace osu.Game.Tests.Visual.Editing // After placement these must be non-default as defaults are read-only. AddAssert("Placed object still has non-default control points", () => - EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT && - EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT); + !ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) && + !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT)); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 27c107a2db..6cf66963a5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -269,7 +269,7 @@ namespace osu.Game.Tests.Visual.UserInterface private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { - if (timingPoints[^1] == current) + if (ReferenceEquals(timingPoints[^1], current)) return current; int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat" @@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.UserInterface { if (timingPoints.Count == 0) return 0; - if (timingPoints[^1] == current) + if (ReferenceEquals(timingPoints[^1], current)) { Debug.Assert(BeatSyncSource.Clock != null); diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 4146bbcc5e..45a935d165 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -127,7 +127,7 @@ namespace osu.Game.Graphics.Containers TimeSinceLastBeat = beatLength - TimeUntilNextBeat; - if (timingPoint == lastTimingPoint && beatIndex == lastBeat) + if (ReferenceEquals(timingPoint, lastTimingPoint) && beatIndex == lastBeat) return; // as this event is sometimes used for sound triggers where `BeginDelayedSequence` has no effect, avoid firing it if too far away from the beat. diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 30b8fe965a..d20e0616e5 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Objects if (legacyInfo != null) DifficultyControlPoint = (DifficultyControlPoint)legacyInfo.DifficultyPointAt(StartTime).DeepClone(); - else if (DifficultyControlPoint == DifficultyControlPoint.DEFAULT) + else if (ReferenceEquals(DifficultyControlPoint, DifficultyControlPoint.DEFAULT)) DifficultyControlPoint = new DifficultyControlPoint(); DifficultyControlPoint.Time = StartTime; @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Objects // This is done here after ApplyDefaultsToSelf as we may require custom defaults to be applied to have an accurate end time. if (legacyInfo != null) SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone(); - else if (SampleControlPoint == SampleControlPoint.DEFAULT) + else if (ReferenceEquals(SampleControlPoint, SampleControlPoint.DEFAULT)) SampleControlPoint = new SampleControlPoint(); SampleControlPoint.Time = this.GetEndTime() + control_point_leniency; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 4e2c018330..ee82ded39d 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts case NotifyCollectionChangedAction.Remove: foreach (var group in args.OldItems.OfType()) { - var matching = Children.SingleOrDefault(gv => gv.Group == group); + var matching = Children.SingleOrDefault(gv => ReferenceEquals(gv.Group, group)); if (matching != null) matching.Expire(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 5d5681ea2b..544c3f8369 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case NotifyCollectionChangedAction.Remove: foreach (var group in args.OldItems.OfType()) { - var matching = Children.SingleOrDefault(gv => gv.Group == group); + var matching = Children.SingleOrDefault(gv => ReferenceEquals(gv.Group, group)); matching?.Expire(); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 34abe81ae2..32ebc9c3c1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -205,7 +205,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateRepeats(repeats); } - if (difficultyControlPoint != Item.DifficultyControlPoint) + if (!ReferenceEquals(difficultyControlPoint, Item.DifficultyControlPoint)) { difficultyControlPoint = Item.DifficultyControlPoint; difficultyOverrideDisplay?.Expire(); @@ -220,7 +220,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - if (sampleControlPoint != Item.SampleControlPoint) + if (!ReferenceEquals(sampleControlPoint, Item.SampleControlPoint)) { sampleControlPoint = Item.SampleControlPoint; sampleOverrideDisplay?.Expire(); @@ -393,7 +393,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.CurrentState.Keyboard.ShiftPressed) { - if (hitObject.DifficultyControlPoint == DifficultyControlPoint.DEFAULT) + if (ReferenceEquals(hitObject.DifficultyControlPoint, DifficultyControlPoint.DEFAULT)) hitObject.DifficultyControlPoint = new DifficultyControlPoint(); double newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index c7df36be77..c3b29afd30 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -141,7 +141,7 @@ namespace osu.Game.Screens.Edit seekTime = timingPoint.Time + closestBeat * seekAmount; } - if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First()) + if (seekTime < timingPoint.Time && !ReferenceEquals(timingPoint, ControlPointInfo.TimingPoints.First())) seekTime = timingPoint.Time; SeekSmoothlyTo(seekTime); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 6d46b429f0..b6e3e91c98 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -65,7 +65,8 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(group => { // TODO: This should scroll the selected row into view. - foreach (var b in BackgroundFlow) b.Selected = b.Item == group.NewValue; + foreach (var b in BackgroundFlow) + b.Selected = ReferenceEquals(b.Item, group.NewValue); }, true); } diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 60cb263a79..c33dcc6e91 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Edit.Timing // Try and create matching types from the currently selected control point. var selected = selectedGroup.Value; - if (selected != null && selected != group) + if (selected != null && !ReferenceEquals(selected, group)) { foreach (var controlPoint in selected.ControlPoints) { diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 67b7abb25b..1cdcfdf167 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Edit.Timing double? offsetChange = newStartTime - selectedGroupStartTime; var nextGroup = editorBeatmap.ControlPointInfo.TimingPoints - .SkipWhile(g => g != tcp) + .SkipWhile(g => !ReferenceEquals(g, tcp)) .Skip(1) .FirstOrDefault(); From b0b3ea42ccf6330125d94da23ef26110f2a3a61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 10:44:14 +0200 Subject: [PATCH 047/363] Remove null default value in `ImportSkinTest` helper method --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index f3adb85006..8b7fcae1a9 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -357,7 +357,7 @@ namespace osu.Game.Tests.Skins.IO } } - private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import = null) + private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import) { var skinManager = osu.Dependencies.Get(); return await skinManager.Import(import); From ebcee21f6f50d640a00badab6013ea332d14338d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 17:52:42 +0900 Subject: [PATCH 048/363] Remove necessity to provide `BeatmapDifficultyCache` as a function to `ScoreManager` --- osu.Game/OsuGameBase.cs | 6 ++++-- osu.Game/Scoring/ScoreManager.cs | 12 +++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f0f364314c..a09588516d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -264,14 +264,16 @@ namespace osu.Game var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); + dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); + // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, () => difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API)); - dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); + // Add after all the above cache operations as it depends on them. AddInternal(difficultyCache); dependencies.Cache(userCache = new UserLookupCache()); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 44aafbec74..027e25d3b3 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -27,16 +27,16 @@ namespace osu.Game.Scoring public class ScoreManager : ModelManager, IModelImporter { private readonly Scheduler scheduler; - private readonly Func difficulties; + private readonly BeatmapDifficultyCache difficultyCache; private readonly OsuConfigManager configManager; private readonly ScoreImporter scoreImporter; public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, - Func difficulties = null, OsuConfigManager configManager = null) + BeatmapDifficultyCache difficultyCache = null, OsuConfigManager configManager = null) : base(storage, realm) { this.scheduler = scheduler; - this.difficulties = difficulties; + this.difficultyCache = difficultyCache; this.configManager = configManager; scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm) @@ -65,8 +65,6 @@ namespace osu.Game.Scoring /// The given ordered by decreasing total score. public async Task OrderByTotalScoreAsync(ScoreInfo[] scores, CancellationToken cancellationToken = default) { - var difficultyCache = difficulties?.Invoke(); - if (difficultyCache != null) { // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. @@ -168,11 +166,11 @@ namespace osu.Game.Scoring return score.BeatmapInfo.MaxCombo.Value; #pragma warning restore CS0618 - if (difficulties == null) + if (difficultyCache == null) return null; // We can compute the max combo locally after the async beatmap difficulty computation. - var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); + var difficulty = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); return difficulty?.MaxCombo; } From dbae4c6f5a9f307bbb4aad65f4fa841b6523e097 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:21:37 +0900 Subject: [PATCH 049/363] `PostImport` -> `PresentImport` --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Database/IPostImports.cs | 2 +- osu.Game/Database/RealmArchiveModelImporter.cs | 6 +++--- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Scoring/ScoreManager.cs | 4 ++-- osu.Game/Skinning/SkinManager.cs | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 06a8ad5188..816808638f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -454,9 +454,9 @@ namespace osu.Game.Beatmaps #region Implementation of IPostImports - public Action>>? PostImport + public Action>>? PresentImport { - set => beatmapImporter.PostImport = value; + set => beatmapImporter.PresentImport = value; } #endregion diff --git a/osu.Game/Database/IPostImports.cs b/osu.Game/Database/IPostImports.cs index 83a211f6e6..d8493d5e18 100644 --- a/osu.Game/Database/IPostImports.cs +++ b/osu.Game/Database/IPostImports.cs @@ -12,6 +12,6 @@ namespace osu.Game.Database /// /// Fired when the user requests to view the resulting import. /// - public Action>>? PostImport { set; } + public Action>>? PresentImport { set; } } } diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index e6956d3184..6d5a10682f 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -65,7 +65,7 @@ namespace osu.Game.Database /// /// Fired when the user requests to view the resulting import. /// - public Action>>? PostImport { get; set; } + public Action>>? PresentImport { get; set; } /// /// Set an endpoint for notifications to be posted to. @@ -158,12 +158,12 @@ namespace osu.Game.Database ? $"Imported {imported.First().GetDisplayString()}!" : $"Imported {imported.Count} {HumanisedModelName}s!"; - if (imported.Count > 0 && PostImport != null) + if (imported.Count > 0 && PresentImport != null) { notification.CompletionText += " Click to view."; notification.CompletionClickAction = () => { - PostImport?.Invoke(imported); + PresentImport?.Invoke(imported); return true; }; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 97476358b1..4732a991a2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -711,13 +711,13 @@ namespace osu.Game SkinManager.PostNotification = n => Notifications.Post(n); BeatmapManager.PostNotification = n => Notifications.Post(n); - BeatmapManager.PostImport = items => PresentBeatmap(items.First().Value); + BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value); BeatmapDownloader.PostNotification = n => Notifications.Post(n); ScoreDownloader.PostNotification = n => Notifications.Post(n); ScoreManager.PostNotification = n => Notifications.Post(n); - ScoreManager.PostImport = items => PresentScore(items.First().Value); + ScoreManager.PresentImport = items => PresentScore(items.First().Value); // make config aware of how to lookup skins for on-screen display purposes. // if this becomes a more common thing, tracked settings should be reconsidered to allow local DI. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 44aafbec74..139675711f 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -269,9 +269,9 @@ namespace osu.Game.Scoring #region Implementation of IPresentImports - public Action>> PostImport + public Action>> PresentImport { - set => scoreImporter.PostImport = value; + set => scoreImporter.PresentImport = value; } #endregion diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 38eff2f0d6..dc0197e613 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -259,9 +259,9 @@ namespace osu.Game.Skinning #region Implementation of IModelImporter - public Action>> PostImport + public Action>> PresentImport { - set => skinImporter.PostImport = value; + set => skinImporter.PresentImport = value; } public Task Import(params string[] paths) => skinImporter.Import(paths); From 45c5013f09d5467d8087e7bd24661cf929aee91d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:38:02 +0900 Subject: [PATCH 050/363] Remove default value of `RealmArchiveModelImporter.HandledExtensions` Not used anywhere and probably not wanted ever. --- osu.Game/Database/RealmArchiveModelImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 6d5a10682f..584dda7264 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -56,7 +56,7 @@ namespace osu.Game.Database /// private static readonly ThreadedTaskScheduler import_scheduler_batch = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(RealmArchiveModelImporter)); - public virtual IEnumerable HandledExtensions => new[] { @".zip" }; + public abstract IEnumerable HandledExtensions { get; } protected readonly RealmFileStore Files; From e732c5a2d750ed0ae6687eb3277d0fc44f8c8578 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:38:37 +0900 Subject: [PATCH 051/363] Add `PostImport` method matching `PreImport` --- osu.Game/Database/RealmArchiveModelImporter.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 584dda7264..27bfaf05be 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -338,6 +338,8 @@ namespace osu.Game.Database transaction.Commit(); } + PostImport(item, realm); + LogForModel(item, @"Import successfully completed!"); } catch (Exception e) @@ -473,6 +475,15 @@ namespace osu.Game.Database { } + /// + /// Perform any final actions after the import has been committed to the database. + /// + /// The model prepared for import. + /// The current realm context. + protected virtual void PostImport(TModel model, Realm realm) + { + } + /// /// Check whether an existing model already exists for a new import item. /// From ba394f283111e19ff274bae8cc4cc93a0cd46110 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:53:48 +0900 Subject: [PATCH 052/363] Remove `IPostsImports` interface (and move to `IModelImporter`) --- osu.Game/Database/IModelImporter.cs | 8 +++++++- osu.Game/Database/IPostImports.cs | 17 ----------------- 2 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 osu.Game/Database/IPostImports.cs diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index bf9f2f9c75..26342cb5fe 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.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; using System.Collections.Generic; using System.Threading.Tasks; using osu.Game.Overlays.Notifications; @@ -11,7 +12,7 @@ namespace osu.Game.Database /// A class which handles importing of associated models to the game store. /// /// The model type. - public interface IModelImporter : IPostNotifications, IPostImports, ICanAcceptFiles + public interface IModelImporter : IPostNotifications, ICanAcceptFiles where TModel : class, IHasGuidPrimaryKey { /// @@ -26,5 +27,10 @@ namespace osu.Game.Database /// A user displayable name for the model type associated with this manager. /// string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; + + /// + /// Fired when the user requests to view the resulting import. + /// + public Action>>? PresentImport { set; } } } diff --git a/osu.Game/Database/IPostImports.cs b/osu.Game/Database/IPostImports.cs deleted file mode 100644 index d8493d5e18..0000000000 --- a/osu.Game/Database/IPostImports.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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; - -namespace osu.Game.Database -{ - public interface IPostImports - where TModel : class, IHasGuidPrimaryKey - { - /// - /// Fired when the user requests to view the resulting import. - /// - public Action>>? PresentImport { set; } - } -} From 58d6fa46453a0787d11ee1fcf02ff26c99ffb9a1 Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Mon, 20 Jun 2022 19:55:26 +0800 Subject: [PATCH 053/363] Reduce NoFail pp multiplier --- .../Difficulty/ManiaPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index dea02adfbf..216dfe6c47 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty double multiplier = 8.0; if (score.Mods.Any(m => m is ModNoFail)) - multiplier *= 0.9; + multiplier *= 0.75; if (score.Mods.Any(m => m is ModEasy)) multiplier *= 0.5; From 34f1c80b7c2dbe6125510f274b42f251be0fb8dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 20:04:21 +0200 Subject: [PATCH 054/363] Add and use `ILinkHandler` interface --- .../Graphics/Containers/LinkFlowContainer.cs | 7 +++-- .../Markdown/OsuMarkdownLinkText.cs | 5 ++-- osu.Game/Online/ILinkHandler.cs | 30 +++++++++++++++++++ osu.Game/OsuGame.cs | 3 +- 4 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Online/ILinkHandler.cs diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index a67e5ae66f..bf96695fd3 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Framework.Platform; +using osu.Game.Online; using osu.Game.Users; namespace osu.Game.Graphics.Containers @@ -25,7 +26,7 @@ namespace osu.Game.Graphics.Containers } [Resolved(CanBeNull = true)] - private OsuGame game { get; set; } + private ILinkHandler linkHandler { get; set; } [Resolved] private GameHost host { get; set; } @@ -81,8 +82,8 @@ namespace osu.Game.Graphics.Containers { if (action != null) action(); - else if (game != null) - game.HandleLink(link); + else if (linkHandler != null) + linkHandler.HandleLink(link); // fallback to handle cases where OsuGame is not available, ie. tournament client. else if (link.Action == LinkAction.External) host.OpenUrlExternally(link.Argument.ToString()); diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs index dca9e3de6e..3a16eb0a0f 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs @@ -8,6 +8,7 @@ using Markdig.Syntax.Inlines; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers.Markdown; +using osu.Game.Online; using osu.Game.Online.Chat; using osu.Game.Overlays; @@ -16,7 +17,7 @@ namespace osu.Game.Graphics.Containers.Markdown public class OsuMarkdownLinkText : MarkdownLinkText { [Resolved(canBeNull: true)] - private OsuGame game { get; set; } + private ILinkHandler linkHandler { get; set; } private readonly string text; private readonly string title; @@ -51,7 +52,7 @@ namespace osu.Game.Graphics.Containers.Markdown }; } - protected override void OnLinkPressed() => game?.HandleLink(Url); + protected override void OnLinkPressed() => linkHandler?.HandleLink(Url); private class OsuMarkdownLinkCompiler : DrawableLinkCompiler { diff --git a/osu.Game/Online/ILinkHandler.cs b/osu.Game/Online/ILinkHandler.cs new file mode 100644 index 0000000000..1b8fad4bd9 --- /dev/null +++ b/osu.Game/Online/ILinkHandler.cs @@ -0,0 +1,30 @@ +// 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.Allocation; +using osu.Game.Online.Chat; + +namespace osu.Game.Online +{ + /// + /// Handle an arbitrary URL. Displays via in-game overlays where possible. + /// Methods can be called from a non-thread-safe non-game-loaded state. + /// + [Cached] + public interface ILinkHandler + { + /// + /// Handle an arbitrary URL. Displays via in-game overlays where possible. + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The URL to load. + void HandleLink(string url); + + /// + /// Handle a specific . + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The link to load. + void HandleLink(LinkDetails link); + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4732a991a2..f51a18b36f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -39,6 +39,7 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Localisation; +using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays; @@ -69,7 +70,7 @@ namespace osu.Game /// The full osu! experience. Builds on top of to add menus and binding logic /// for initial components that are generally retrieved via DI. /// - public class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager + public class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler { /// /// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications). From 92011124d146be3074b8a1df18e1b0a0f8f5cb35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 20:08:36 +0200 Subject: [PATCH 055/363] Implement IPC channel for `osu://` scheme links --- osu.Game/IPC/OsuSchemeLinkIPCChannel.cs | 49 +++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 osu.Game/IPC/OsuSchemeLinkIPCChannel.cs diff --git a/osu.Game/IPC/OsuSchemeLinkIPCChannel.cs b/osu.Game/IPC/OsuSchemeLinkIPCChannel.cs new file mode 100644 index 0000000000..33318e329c --- /dev/null +++ b/osu.Game/IPC/OsuSchemeLinkIPCChannel.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using System.Threading.Tasks; +using osu.Framework.Platform; +using osu.Game.Online; + +namespace osu.Game.IPC +{ + public class OsuSchemeLinkIPCChannel : IpcChannel + { + private readonly ILinkHandler? linkHandler; + + public OsuSchemeLinkIPCChannel(IIpcHost host, ILinkHandler? linkHandler = null) + : base(host) + { + this.linkHandler = linkHandler; + + MessageReceived += msg => + { + Debug.Assert(linkHandler != null); + linkHandler.HandleLink(msg.Link); + return null; + }; + } + + public async Task HandleLinkAsync(string url) + { + if (linkHandler == null) + { + await SendMessageAsync(new OsuSchemeLinkMessage(url)).ConfigureAwait(false); + return; + } + + linkHandler.HandleLink(url); + } + } + + public class OsuSchemeLinkMessage + { + public string Link { get; } + + public OsuSchemeLinkMessage(string link) + { + Link = link; + } + } +} From 330af7ec7460c0cc49ff7a13f7597c18459650f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 21:21:16 +0200 Subject: [PATCH 056/363] Handle `osu://` scheme links via IPC in desktop game --- osu.Desktop/OsuGameDesktop.cs | 31 +++++++++++++-------- osu.Desktop/Program.cs | 42 ++++++++++++++++++++--------- osu.Game/IPC/IPCTimeoutException.cs | 15 +++++++++++ 3 files changed, 65 insertions(+), 23 deletions(-) create mode 100644 osu.Game/IPC/IPCTimeoutException.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 6713136343..873be052d7 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -22,23 +20,26 @@ using osu.Game.Updater; using osu.Desktop.Windows; using osu.Framework.Threading; using osu.Game.IO; +using osu.Game.IPC; namespace osu.Desktop { internal class OsuGameDesktop : OsuGame { - public OsuGameDesktop(string[] args = null) + private OsuSchemeLinkIPCChannel? osuSchemeLinkIPCChannel; + + public OsuGameDesktop(string[]? args = null) : base(args) { } - public override StableStorage GetStorageForStableInstall() + public override StableStorage? GetStorageForStableInstall() { try { if (Host is DesktopGameHost desktopHost) { - string stablePath = getStableInstallPath(); + string? stablePath = getStableInstallPath(); if (!string.IsNullOrEmpty(stablePath)) return new StableStorage(stablePath, desktopHost); } @@ -51,11 +52,11 @@ namespace osu.Desktop return null; } - private string getStableInstallPath() + private string? getStableInstallPath() { static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")) || File.Exists(Path.Combine(p, "osu!.cfg")); - string stableInstallPath; + string? stableInstallPath; if (OperatingSystem.IsWindows()) { @@ -83,15 +84,15 @@ namespace osu.Desktop } [SupportedOSPlatform("windows")] - private string getStableInstallPathFromRegistry() + private string? getStableInstallPathFromRegistry() { - using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) + using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu")) return key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", ""); } protected override UpdateManager CreateUpdateManager() { - string packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER"); + string? packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER"); if (!string.IsNullOrEmpty(packageManaged)) return new NoActionUpdateManager(); @@ -118,6 +119,8 @@ namespace osu.Desktop LoadComponentAsync(new GameplayWinKeyBlocker(), Add); LoadComponentAsync(new ElevatedPrivilegesChecker(), Add); + + osuSchemeLinkIPCChannel = new OsuSchemeLinkIPCChannel(Host, this); } public override void SetHost(GameHost host) @@ -135,7 +138,7 @@ namespace osu.Desktop } private readonly List importableFiles = new List(); - private ScheduledDelegate importSchedule; + private ScheduledDelegate? importSchedule; private void fileDrop(string[] filePaths) { @@ -168,5 +171,11 @@ namespace osu.Desktop Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + osuSchemeLinkIPCChannel?.Dispose(); + } } } diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 4ba9cc6a90..7a5809e915 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -11,6 +11,7 @@ using osu.Framework; using osu.Framework.Development; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game; using osu.Game.IPC; using osu.Game.Tournament; using Squirrel; @@ -65,19 +66,8 @@ namespace osu.Desktop { if (!host.IsPrimaryInstance) { - if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args - { - var importer = new ArchiveImportIPCChannel(host); - - foreach (string file in args) - { - Console.WriteLine(@"Importing {0}", file); - if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000)) - throw new TimeoutException(@"IPC took too long to send"); - } - + if (trySendIPCMessage(host, cwd, args)) return; - } // we want to allow multiple instances to be started when in debug. if (!DebugUtils.IsDebugBuild) @@ -108,6 +98,34 @@ namespace osu.Desktop } } + private static bool trySendIPCMessage(IIpcHost host, string cwd, string[] args) + { + if (args.Length == 1 && args[0].StartsWith(OsuGameBase.OSU_PROTOCOL, StringComparison.Ordinal)) + { + var osuSchemeLinkHandler = new OsuSchemeLinkIPCChannel(host); + if (!osuSchemeLinkHandler.HandleLinkAsync(args[0]).Wait(3000)) + throw new IPCTimeoutException(osuSchemeLinkHandler.GetType()); + + return true; + } + + if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args + { + var importer = new ArchiveImportIPCChannel(host); + + foreach (string file in args) + { + Console.WriteLine(@"Importing {0}", file); + if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000)) + throw new IPCTimeoutException(importer.GetType()); + } + + return true; + } + + return false; + } + [SupportedOSPlatform("windows")] private static void setupSquirrel() { diff --git a/osu.Game/IPC/IPCTimeoutException.cs b/osu.Game/IPC/IPCTimeoutException.cs new file mode 100644 index 0000000000..d820184468 --- /dev/null +++ b/osu.Game/IPC/IPCTimeoutException.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.IPC +{ + public class IPCTimeoutException : TimeoutException + { + public IPCTimeoutException(Type channelType) + : base($@"IPC took too long to send message via channel {channelType}") + { + } + } +} From 21d60231b6eecb087d5d10873e80aaf208115c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 22:10:25 +0200 Subject: [PATCH 057/363] Add limited test coverage of `osu://` scheme link IPC flow --- .../TestSceneInterProcessCommunication.cs | 86 +++++++++++++++++++ osu.Game/Tests/Visual/OsuGameTestScene.cs | 2 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs new file mode 100644 index 0000000000..f5af077696 --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs @@ -0,0 +1,86 @@ +// 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; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.IPC; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Navigation +{ + [TestFixture] + [Ignore("This test cannot be run headless, as it requires the game host running the nested game to have IPC bound.")] + public class TestSceneInterProcessCommunication : OsuGameTestScene + { + private HeadlessGameHost ipcSenderHost = null!; + + private OsuSchemeLinkIPCChannel osuSchemeLinkIPCReceiver = null!; + private OsuSchemeLinkIPCChannel osuSchemeLinkIPCSender = null!; + + private const int requested_beatmap_set_id = 1; + + [Resolved] + private GameHost gameHost { get; set; } = null!; + + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("set up request handling", () => + { + ((DummyAPIAccess)API).HandleRequest = request => + { + switch (request) + { + case GetBeatmapSetRequest gbr: + + var apiBeatmapSet = CreateAPIBeatmapSet(); + apiBeatmapSet.OnlineID = requested_beatmap_set_id; + apiBeatmapSet.Beatmaps = apiBeatmapSet.Beatmaps.Append(new APIBeatmap + { + DifficultyName = "Target difficulty", + OnlineID = 75, + }).ToArray(); + gbr.TriggerSuccess(apiBeatmapSet); + return true; + } + + return false; + }; + }); + AddStep("create IPC receiver channel", () => osuSchemeLinkIPCReceiver = new OsuSchemeLinkIPCChannel(gameHost, Game)); + AddStep("create IPC sender channel", () => + { + ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { BindIPC = true }); + osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost); + }); + } + + [Test] + public void TestOsuSchemeLinkIPCChannel() + { + AddStep("open beatmap via IPC", () => osuSchemeLinkIPCSender.HandleLinkAsync($@"osu://s/{requested_beatmap_set_id}").WaitSafely()); + AddUntilStep("beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible); + AddUntilStep("beatmap overlay showing content", () => Game.ChildrenOfType().FirstOrDefault()?.Header.BeatmapSet.Value.OnlineID == requested_beatmap_set_id); + } + + public override void TearDownSteps() + { + AddStep("dispose IPC receiver", () => osuSchemeLinkIPCReceiver.Dispose()); + AddStep("dispose IPC sender", () => + { + osuSchemeLinkIPCReceiver.Dispose(); + ipcSenderHost.Dispose(); + }); + base.TearDownSteps(); + } + } +} diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index df0bdb0a50..b9f6183869 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual } [TearDownSteps] - public void TearDownSteps() + public virtual void TearDownSteps() { if (DebugUtils.IsNUnitRunning && Game != null) { From 345ae7bbc3ec287f60d5a38fb2202d674aad98b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 00:46:52 +0200 Subject: [PATCH 058/363] Fix build errors after implicit NRT enable --- osu.Android/AndroidMouseSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android/AndroidMouseSettings.cs b/osu.Android/AndroidMouseSettings.cs index 7dff929cd4..54b787fd17 100644 --- a/osu.Android/AndroidMouseSettings.cs +++ b/osu.Android/AndroidMouseSettings.cs @@ -20,11 +20,11 @@ namespace osu.Android protected override LocalisableString Header => MouseSettingsStrings.Mouse; - private Bindable handlerSensitivity; + private Bindable handlerSensitivity = null!; - private Bindable localSensitivity; + private Bindable localSensitivity = null!; - private Bindable relativeMode; + private Bindable relativeMode = null!; public AndroidMouseSettings(AndroidMouseHandler mouseHandler) { From 6a2646168334d55582a380860aaa6e61270e6f45 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 12:05:28 +0900 Subject: [PATCH 059/363] Compare by reference in ControlPoint.Equals() --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index b91d609849..56a432aec4 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -52,8 +52,12 @@ namespace osu.Game.Beatmaps.ControlPoints && Equals(otherControlPoint); public virtual bool Equals(ControlPoint? other) - => other != null - && Time == other.Time; + { + if (ReferenceEquals(other, null)) return false; + if (ReferenceEquals(other, this)) return true; + + return Time == other.Time; + } // ReSharper disable once NonReadonlyMemberInGetHashCode public override int GetHashCode() => Time.GetHashCode(); From 9763a583924d0a68d3c70e07a144d480f8bf9b01 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 12:05:52 +0900 Subject: [PATCH 060/363] Change to use ReferenceEquals --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index b6e3e91c98..b923957273 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Timing { BackgroundFlow.Add(new RowBackground(group) { - Selected = group.Equals(selectedGroup?.Value), + Selected = ReferenceEquals(group, selectedGroup?.Value), Action = () => { selectedGroup.Value = group; From 93ce6fc98149a28ecc45de1a6dfba7be38fc6ec7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 12:11:44 +0900 Subject: [PATCH 061/363] Remove redundant diff processing --- osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index 01bc01bf5b..b4647c2b64 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -41,17 +41,12 @@ namespace osu.Game.Screens.Edit editorBeatmap.BeginChange(); processHitObjects(result, () => newBeatmap ??= readBeatmap(newState)); - processTimingPoints(result, () => newBeatmap ??= readBeatmap(newState)); + processTimingPoints(() => newBeatmap ??= readBeatmap(newState)); editorBeatmap.EndChange(); } - private void processTimingPoints(DiffResult result, Func getNewBeatmap) + private void processTimingPoints(Func getNewBeatmap) { - findChangedIndices(result, LegacyDecoder.Section.TimingPoints, out var removedIndices, out var addedIndices); - - if (removedIndices.Count == 0 && addedIndices.Count == 0) - return; - ControlPointInfo newControlPoints = EditorBeatmap.ConvertControlPoints(getNewBeatmap().ControlPointInfo); // Remove all groups from the current beatmap which don't have a corresponding equal group in the new beatmap. From b3e5642dfdd7298bebfb51d0a9b17211c940d80c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 12:23:41 +0900 Subject: [PATCH 062/363] Use WaitingOnFrames instead --- osu.Game/Screens/Play/SpectatorPlayer.cs | 26 +++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 72a7d3c8a1..797787b194 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -45,6 +45,20 @@ namespace osu.Game.Screens.Play }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + DrawableRuleset.FrameStableClock.WaitingOnFrames.BindValueChanged(waiting => + { + if (GameplayClockContainer is MasterGameplayClockContainer master) + { + if (master.UserPlaybackRate.Value > 1 && waiting.NewValue) + master.UserPlaybackRate.Value = 1; + } + }, true); + } + protected override void StartGameplay() { base.StartGameplay(); @@ -83,18 +97,6 @@ namespace osu.Game.Screens.Play SetGameplayStartTime(score.Replay.Frames[0].Time); } - protected override void Update() - { - base.Update(); - - if (HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.Value > 1 - && score.Replay.Frames.Count > 0 - && DrawableRuleset.FrameStableClock.CurrentTime >= score.Replay.Frames[^1].Time) - { - HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.Value = 1; - } - } - protected override Score CreateScore(IBeatmap beatmap) => score; protected override ResultsScreen CreateResults(ScoreInfo score) From 046b848bcd1c37a18b8ddf2141642a28668f5a6e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 12:53:06 +0900 Subject: [PATCH 063/363] Split group selection to separate method --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index b923957273..b5c162ab11 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -44,7 +44,6 @@ namespace osu.Game.Screens.Edit.Timing { BackgroundFlow.Add(new RowBackground(group) { - Selected = ReferenceEquals(group, selectedGroup?.Value), Action = () => { selectedGroup.Value = group; @@ -55,6 +54,8 @@ namespace osu.Game.Screens.Edit.Timing Columns = createHeaders(); Content = value.Select(createContent).ToArray().ToRectangular(); + + updateSelectedGroup(); } } @@ -65,11 +66,17 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(group => { // TODO: This should scroll the selected row into view. - foreach (var b in BackgroundFlow) - b.Selected = ReferenceEquals(b.Item, group.NewValue); + updateSelectedGroup(); }, true); } + private void updateSelectedGroup() + { + // TODO: This should scroll the selected row into view. + foreach (var b in BackgroundFlow) + b.Selected = ReferenceEquals(b.Item, selectedGroup?.Value); + } + private TableColumn[] createHeaders() { var columns = new List From c61e90d7685a9709a74ebcf077b99f66da09a6a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 13:07:50 +0900 Subject: [PATCH 064/363] Change editor seek-while-playing modifier to be BPM agnostic --- osu.Game/Screens/Edit/Editor.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d0d4c0ba1e..7a9dae33b4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -798,10 +798,11 @@ namespace osu.Game.Screens.Edit if (trackPlaying) { - // generally users are not looking to perform tiny seeks when the track is playing, - // so seeks should always be by one full beat, bypassing the beatDivisor. + // generally users are not looking to perform tiny seeks when the track is playing. // this multiplication undoes the division that will be applied in the underlying seek operation. - amount *= beatDivisor.Value; + // scale by BPM to keep the seek amount constant across all BPMs. + var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(clock.CurrentTimeAccurate); + amount *= beatDivisor.Value * (timingPoint.BPM / 120); } if (direction < 1) From 10efb4bc6c0976866261c584aa10885162727bca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 14:08:30 +0900 Subject: [PATCH 065/363] Fix incorrect disposal in teardown steps --- .../Visual/Navigation/TestSceneInterProcessCommunication.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs index f5af077696..1c2b1fe37d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("dispose IPC receiver", () => osuSchemeLinkIPCReceiver.Dispose()); AddStep("dispose IPC sender", () => { - osuSchemeLinkIPCReceiver.Dispose(); + osuSchemeLinkIPCSender.Dispose(); ipcSenderHost.Dispose(); }); base.TearDownSteps(); From 88f450e1d0b3e0beb982f74cb1fa4936694098fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 14:40:21 +0900 Subject: [PATCH 066/363] Remove accidental `nullable enable` spec --- .../Screens/Utility/SampleComponents/LatencyCursorContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs b/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs index 656aa6a8b3..aaf837eb60 100644 --- a/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs +++ b/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable enable - using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; From 42701757c340037116441a31b80c2aff9ff2a866 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 14:52:57 +0900 Subject: [PATCH 067/363] Fix latch reset logic --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 33325951e5..cdfdc096d4 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; @@ -219,6 +220,8 @@ namespace osu.Game.Screens.Edit.Timing private readonly BindableInt interpolatedBpm = new BindableInt(); + private ScheduledDelegate latchDelegate; + protected override void LoadComplete() { base.LoadComplete(); @@ -266,12 +269,9 @@ namespace osu.Game.Screens.Edit.Timing using (swing.BeginDelayedSequence(350)) { swing.RotateTo(0, 1000, Easing.OutQuint); - swing.Delay(250).Schedule(() => - { - // prevent playing latch sound if metronome has started back up again - if (!isSwinging) - latch?.Play(); - }); + + using (swing.BeginDelayedSequence(250)) + latchDelegate = Schedule(() => latch?.Play()); } } } @@ -287,6 +287,9 @@ namespace osu.Game.Screens.Edit.Timing isSwinging = true; + latchDelegate?.Cancel(); + latchDelegate = null; + float currentAngle = swing.Rotation; float targetAngle = currentAngle > 0 ? -angle : angle; From 7b46d383186eb28e3bfd43cbfd2971fd37cb69a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 15:02:43 +0900 Subject: [PATCH 068/363] Move colour fade back to where it was --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 5a6a478ca1..4aed7527af 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -266,7 +266,6 @@ namespace osu.Game.Screens.Edit.Timing if (BeatSyncSource.Clock?.IsRunning != true && isSwinging) { swing.ClearTransforms(true); - stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); isSwinging = false; @@ -284,6 +283,7 @@ namespace osu.Game.Screens.Edit.Timing using (swing.BeginDelayedSequence(350)) { swing.RotateTo(0, 1000, Easing.OutQuint); + stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); using (swing.BeginDelayedSequence(250)) latchDelegate = Schedule(() => latch?.Play()); From 16a9e1881503dbb10f8d4cf885c76ec1a46ef7b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 15:02:53 +0900 Subject: [PATCH 069/363] Adjust timing of latch to be more in sync with visuals --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 4aed7527af..62580ac527 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -280,12 +280,12 @@ namespace osu.Game.Screens.Edit.Timing return; } - using (swing.BeginDelayedSequence(350)) + using (BeginDelayedSequence(350)) { swing.RotateTo(0, 1000, Easing.OutQuint); stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); - using (swing.BeginDelayedSequence(250)) + using (BeginDelayedSequence(380)) latchDelegate = Schedule(() => latch?.Play()); } } From 82c4d855d493f6a36cba8b2c7436a1ecf92c059e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 15:05:17 +0900 Subject: [PATCH 070/363] Rename sample variable to be easier to discern from other fields --- .../Screens/Edit/Timing/MetronomeDisplay.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 62580ac527..22a15a291e 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -37,12 +37,12 @@ namespace osu.Game.Screens.Edit.Timing private IAdjustableClock metronomeClock; - private Sample tick; - private Sample tickDownbeat; - private Sample latch; + private Sample sampleTick; + private Sample sampleTickDownbeat; + private Sample sampleLatch; [CanBeNull] - private ScheduledDelegate clunkDelegate; + private ScheduledDelegate tickPlaybackDelegate; [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } @@ -57,9 +57,9 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load(AudioManager audio) { - tick = audio.Samples.Get(@"UI/metronome-tick"); - tickDownbeat = audio.Samples.Get(@"UI/metronome-tick-downbeat"); - latch = audio.Samples.Get(@"UI/metronome-latch"); + sampleTick = audio.Samples.Get(@"UI/metronome-tick"); + sampleTickDownbeat = audio.Samples.Get(@"UI/metronome-tick-downbeat"); + sampleLatch = audio.Samples.Get(@"UI/metronome-latch"); const float taper = 25; const float swing_vertical_offset = -23; @@ -269,14 +269,14 @@ namespace osu.Game.Screens.Edit.Timing isSwinging = false; - clunkDelegate?.Cancel(); - clunkDelegate = null; + tickPlaybackDelegate?.Cancel(); + tickPlaybackDelegate = null; // instantly latch if pendulum arm is close enough to center (to prevent awkward delayed playback of latch sound) if (Precision.AlmostEquals(swing.Rotation, 0, 1)) { swing.RotateTo(0); - latch?.Play(); + sampleLatch?.Play(); return; } @@ -286,7 +286,7 @@ namespace osu.Game.Screens.Edit.Timing stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); using (BeginDelayedSequence(380)) - latchDelegate = Schedule(() => latch?.Play()); + latchDelegate = Schedule(() => sampleLatch?.Play()); } } } @@ -316,12 +316,12 @@ namespace osu.Game.Screens.Edit.Timing { stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); - clunkDelegate = Schedule(() => + tickPlaybackDelegate = Schedule(() => { if (!EnableClicking) return; - var channel = beatIndex % timingPoint.TimeSignature.Numerator == 0 ? tickDownbeat?.GetChannel() : tick?.GetChannel(); + var channel = beatIndex % timingPoint.TimeSignature.Numerator == 0 ? sampleTickDownbeat?.GetChannel() : sampleTick?.GetChannel(); if (channel == null) return; From a40ad6f784aa5b16722017176e2c221b226b4908 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 15:07:35 +0900 Subject: [PATCH 071/363] Add slight transform when resetting arm from almost-zero --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 22a15a291e..37fc38404f 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -275,7 +275,7 @@ namespace osu.Game.Screens.Edit.Timing // instantly latch if pendulum arm is close enough to center (to prevent awkward delayed playback of latch sound) if (Precision.AlmostEquals(swing.Rotation, 0, 1)) { - swing.RotateTo(0); + swing.RotateTo(0, 60, Easing.OutQuint); sampleLatch?.Play(); return; } From 0f6f000188680ec7a375b3b244d7abe8557376ef Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 21 Jun 2022 09:13:44 +0100 Subject: [PATCH 072/363] Remove difficulty spike nerf --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 14d13ec785..71dbc026f2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double skillMultiplier => 0.05; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; + protected override int ReducedSectionCount => -1; private double currentStrain; From f74b4ac2771edbeee3eebae2148db8a0f8a18508 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 17:15:25 +0900 Subject: [PATCH 073/363] Fix blocking overhead when calling `WriteAsync` --- osu.Game/Database/RealmAccess.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 865a8b5021..d02f8fe6b2 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -385,11 +385,22 @@ namespace osu.Game.Database /// Write changes to realm asynchronously, guaranteeing order of execution. /// /// The work to run. - public async Task WriteAsync(Action action) + public Task WriteAsync(Action action) { - total_writes_async.Value++; - using (var realm = getRealmInstance()) - await realm.WriteAsync(() => action(realm)); + // Regardless of calling Realm.GetInstance of Realm.GetInstanceAsync, there is a blocking overhead on retrieval. + // Adding a forced Task.Run resolves this. + + return Task.Run(async () => + { + total_writes_async.Value++; + + // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking + // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync + // server, which we don't use. May want to report upstream or revisit in the future. + using (var realm = getRealmInstance()) + // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). + await realm.WriteAsync(() => action(realm)); + }); } /// From 630bd244d5af115226bac1a25d3e245c193ea571 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:01:11 +0100 Subject: [PATCH 074/363] Inherit StrainSkill instead --- .../Difficulty/Skills/Flashlight.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 71dbc026f2..f64c218c65 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -6,6 +6,7 @@ using System; using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; using osu.Game.Rulesets.Osu.Mods; @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// /// Represents the skill required to memorise and hit every object in a map with the Flashlight mod enabled. /// - public class Flashlight : OsuStrainSkill + public class Flashlight : StrainSkill { private readonly bool hasHiddenMod; @@ -27,8 +28,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double skillMultiplier => 0.05; private double strainDecayBase => 0.15; - protected override double DecayWeight => 1.0; - protected override int ReducedSectionCount => -1; private double currentStrain; @@ -43,5 +42,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return currentStrain; } + + public override double DifficultyValue() + { + double difficulty = GetCurrentStrainPeaks().Sum(); + + // 1.06 comes from the default DifficultyMultiplier field in OsuStrainSkill, + // and it's added here to keep values the same after Flashlight was converted from OsuStrainSkill. + return difficulty * 1.06; + } } } From 043aa82a3195a9724256332e82b90c6d139493e3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 18:09:00 +0900 Subject: [PATCH 075/363] Add test --- .../TestSceneSliderFollowCircleInput.cs | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs new file mode 100644 index 0000000000..7a6e19575e --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs @@ -0,0 +1,120 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [HeadlessTest] + public class TestSceneSliderFollowCircleInput : RateAdjustedBeatmapTestScene + { + private List? judgementResults; + private ScoreAccessibleReplayPlayer? currentPlayer; + + [Test] + public void TestMaximumDistanceTrackingWithoutMovement( + [Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)] + float circleSize, + [Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)] + double velocity) + { + const double time_slider_start = 1000; + + float circleRadius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (circleSize - 5) / 5) / 2; + float followCircleRadius = circleRadius * 1.2f; + + performTest(new Beatmap + { + HitObjects = + { + new Slider + { + StartTime = time_slider_start, + Position = new Vector2(0, 0), + DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = velocity }, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(followCircleRadius, 0), + }, followCircleRadius), + }, + }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty + { + CircleSize = circleSize, + SliderTickRate = 1 + }, + Ruleset = new OsuRuleset().RulesetInfo + }, + }, new List + { + new OsuReplayFrame { Position = new Vector2(-circleRadius + 1, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start }, + }); + + AddAssert("Tracking kept", assertMaxJudge); + } + + private bool assertMaxJudge() => judgementResults?.Any() == true && judgementResults.All(t => t.Type == t.Judgement.MaxResult); + + private void performTest(Beatmap beatmap, List frames) + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(beatmap); + + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + + p.OnLoadComplete += _ => + { + p.ScoreProcessor.NewJudgement += result => + { + if (currentPlayer == p) judgementResults?.Add(result); + }; + }; + + LoadScreen(currentPlayer = p); + judgementResults = new List(); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for completion", () => currentPlayer?.ScoreProcessor.HasCompleted.Value == true); + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, new PlayerConfiguration + { + AllowPause = false, + ShowResults = false, + }) + { + } + } + } +} From ede5ca31a1a40bda61922301f625997ca687ae48 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 18:09:23 +0900 Subject: [PATCH 076/363] Always track final follow circle size --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 7 ------ .../Skinning/Default/SliderBall.cs | 25 ++++++++----------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 8a15d730cd..00009f4c3d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -30,9 +30,6 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Apply classic note lock", "Applies note lock to the full hit window.")] public Bindable ClassicNoteLock { get; } = new BindableBool(true); - [SettingSource("Use fixed slider follow circle hit area", "Makes the slider follow circle track its final size at all times.")] - public Bindable FixedFollowCircleHitArea { get; } = new BindableBool(true); - [SettingSource("Always play a slider's tail sample", "Always plays a slider's tail sample regardless of whether it was hit or not.")] public Bindable AlwaysPlayTailSample { get; } = new BindableBool(true); @@ -62,10 +59,6 @@ namespace osu.Game.Rulesets.Osu.Mods { switch (obj) { - case DrawableSlider slider: - slider.Ball.InputTracksVisualSize = !FixedFollowCircleHitArea.Value; - break; - case DrawableSliderHead head: head.TrackFollowCircle = !NoSliderHeadMovement.Value; break; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index d2ea8f1660..389e9343e7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -34,13 +34,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default set => ball.Colour = value; } - /// - /// Whether to track accurately to the visual size of this . - /// If false, tracking will be performed at the final scale at all times. - /// - public bool InputTracksVisualSize = true; - private readonly Drawable followCircle; + private readonly Drawable fullSizeFollowCircle; private readonly DrawableSlider drawableSlider; private readonly Drawable ball; @@ -62,6 +57,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Alpha = 0, Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()), }, + fullSizeFollowCircle = new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true + }, ball = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall()) { Anchor = Anchor.Centre, @@ -104,14 +106,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default tracking = value; - if (InputTracksVisualSize) - followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); - else - { - // We need to always be tracking the final size, at both endpoints. For now, this is achieved by removing the scale duration. - followCircle.ScaleTo(tracking ? 2.4f : 1f); - } + fullSizeFollowCircle.Scale = new Vector2(tracking ? 2.4f : 1f); + followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); } } @@ -170,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default // in valid time range Time.Current >= drawableSlider.HitObject.StartTime && Time.Current < drawableSlider.HitObject.EndTime && // in valid position range - lastScreenSpaceMousePosition.HasValue && followCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && + lastScreenSpaceMousePosition.HasValue && fullSizeFollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && // valid action (actions?.Any(isValidTrackingAction) ?? false); From 2e3d8d7e5d347a45e6cf97476beb7444f82dd1fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Jun 2022 18:13:14 +0900 Subject: [PATCH 077/363] 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 c82365d750..caea787e22 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 d820be4bdc..652dc2740f 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 a8c59d676d..0c3c38f9f0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From a7de43ade6459fe63d85f75d40cb7ea08b7d7e81 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 19:31:44 +0900 Subject: [PATCH 078/363] Add attribute ID --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 72c9608697..b24ff54b8a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -101,6 +101,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty); yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor); + yield return (ATTRIB_ID_SPEED_NOTE_COUNT, SpeedNoteCount); } public override void FromDatabaseAttributes(IReadOnlyDictionary values) @@ -115,6 +116,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty StarRating = values[ATTRIB_ID_DIFFICULTY]; FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; + SpeedNoteCount = values[ATTRIB_ID_SPEED_NOTE_COUNT]; } #region Newtonsoft.Json implicit ShouldSerialize() methods diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index e0844a5c8a..90c8f94ed0 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Difficulty protected const int ATTRIB_ID_SCORE_MULTIPLIER = 15; protected const int ATTRIB_ID_FLASHLIGHT = 17; protected const int ATTRIB_ID_SLIDER_FACTOR = 19; + protected const int ATTRIB_ID_SPEED_NOTE_COUNT = 21; /// /// The mods which were applied to the beatmap. From 73124d2b1fb09523ac8b16ea1d86c6cd1ef3cb1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 12:49:01 +0200 Subject: [PATCH 079/363] Encapsulate mod hotkey selection logic in strategy pattern --- .../UserInterface/TestSceneModColumn.cs | 2 +- .../Overlays/Mods/Input/IModHotkeyHandler.cs | 22 +++++++ .../Overlays/Mods/Input/ModHotkeyHandler.cs | 30 ++++++++++ .../Mods/Input/NoopModHotkeyHandler.cs | 17 ++++++ .../Mods/Input/SequentialModHotkeyHandler.cs | 58 +++++++++++++++++++ osu.Game/Overlays/Mods/ModColumn.cs | 21 +++---- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 13 ++--- .../Overlays/Mods/UserModSelectOverlay.cs | 10 +--- .../OnlinePlay/FreeModSelectOverlay.cs | 3 +- 9 files changed, 145 insertions(+), 31 deletions(-) create mode 100644 osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs create mode 100644 osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs create mode 100644 osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs create mode 100644 osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 50817bf804..f98f39d445 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.UserInterface { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(30), - Child = column = new ModColumn(ModType.DifficultyReduction, true, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }) + Child = column = new ModColumn(ModType.DifficultyReduction, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs new file mode 100644 index 0000000000..aec16ff764 --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// Encapsulates strategies of handling mod hotkeys on the . + /// + public interface IModHotkeyHandler + { + /// + /// Attempt to handle a press of the supplied as a selection of one of the mods in . + /// + /// The key that was pressed by the user. + /// The list of currently available mods. + /// Whether the was handled as a mod selection/deselection. + bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods); + } +} diff --git a/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs new file mode 100644 index 0000000000..22de557979 --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs @@ -0,0 +1,30 @@ +// 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.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// Static factory class for s. + /// + public static class ModHotkeyHandler + { + /// + /// Creates an appropriate for the given . + /// + public static IModHotkeyHandler Create(ModType modType) + { + switch (modType) + { + case ModType.DifficultyReduction: + case ModType.DifficultyIncrease: + case ModType.Automation: + return SequentialModHotkeyHandler.Create(modType); + + default: + return new NoopModHotkeyHandler(); + } + } + } +} diff --git a/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs new file mode 100644 index 0000000000..81152226da --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// A no-op implementation of . + /// Used when a column is not handling any hotkeys at all. + /// + public class NoopModHotkeyHandler : IModHotkeyHandler + { + public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) => false; + } +} diff --git a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs new file mode 100644 index 0000000000..45cfa60fff --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Rulesets.Mods; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// This implementation of receives a sequence of s, + /// and maps the sequence of keys onto the items it is provided in . + /// In this case, particular mods are not bound to particular keys, the hotkeys are a byproduct of mod ordering. + /// + public class SequentialModHotkeyHandler : IModHotkeyHandler + { + public static SequentialModHotkeyHandler Create(ModType modType) + { + switch (modType) + { + case ModType.DifficultyReduction: + return new SequentialModHotkeyHandler(new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }); + + case ModType.DifficultyIncrease: + return new SequentialModHotkeyHandler(new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }); + + case ModType.Automation: + return new SequentialModHotkeyHandler(new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }); + + default: + throw new ArgumentOutOfRangeException(nameof(modType), modType, $"Cannot create {nameof(SequentialModHotkeyHandler)} for provided mod type"); + } + } + + private readonly Key[] toggleKeys; + + private SequentialModHotkeyHandler(Key[] keys) + { + toggleKeys = keys; + } + + public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) + { + int index = Array.IndexOf(toggleKeys, hotkey); + if (index < 0) + return false; + + var modState = availableMods.ElementAtOrDefault(index); + if (modState == null || modState.Filtered.Value) + return false; + + modState.Active.Toggle(); + return true; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 563e9a8d55..998437a0a0 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -21,10 +21,10 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Overlays.Mods.Input; using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Overlays.Mods { @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); - private readonly Key[]? toggleKeys; + private readonly IModHotkeyHandler hotkeyHandler; private readonly TextFlowContainer headerText; private readonly Box headerBackground; @@ -86,10 +86,10 @@ namespace osu.Game.Overlays.Mods private const float header_height = 42; - public ModColumn(ModType modType, bool allowBulkSelection, Key[]? toggleKeys = null) + public ModColumn(ModType modType, bool allowBulkSelection) { ModType = modType; - this.toggleKeys = toggleKeys; + hotkeyHandler = ModHotkeyHandler.Create(modType); Width = 320; RelativeSizeAxes = Axes.Y; @@ -425,17 +425,10 @@ namespace osu.Game.Overlays.Mods protected override bool OnKeyDown(KeyDownEvent e) { - if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false; - if (toggleKeys == null) return false; + if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.Repeat) + return false; - int index = Array.IndexOf(toggleKeys, e.Key); - if (index < 0) return false; - - var modState = availableMods.ElementAtOrDefault(index); - if (modState == null || modState.Filtered.Value) return false; - - modState.Active.Toggle(); - return true; + return hotkeyHandler.HandleHotkeyPressed(e.Key, availableMods); } #endregion diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ad02f079a5..811db393d6 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -21,7 +21,6 @@ using osu.Game.Localisation; using osu.Game.Rulesets.Mods; using osu.Game.Utils; using osuTK; -using osuTK.Input; namespace osu.Game.Overlays.Mods { @@ -68,7 +67,7 @@ namespace osu.Game.Overlays.Mods /// protected virtual bool AllowCustomisation => true; - protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys); + protected virtual ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, false); protected virtual IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) => newSelection; @@ -160,9 +159,9 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Bottom = 10 }, Children = new[] { - createModColumnContent(ModType.DifficultyReduction, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }), - createModColumnContent(ModType.DifficultyIncrease, new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }), - createModColumnContent(ModType.Automation, new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }), + createModColumnContent(ModType.DifficultyReduction), + createModColumnContent(ModType.DifficultyIncrease), + createModColumnContent(ModType.Automation), createModColumnContent(ModType.Conversion), createModColumnContent(ModType.Fun) } @@ -264,9 +263,9 @@ namespace osu.Game.Overlays.Mods column.DeselectAll(); } - private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null) + private ColumnDimContainer createModColumnContent(ModType modType) { - var column = CreateModColumn(modType, toggleKeys).With(column => + var column = CreateModColumn(modType).With(column => { // spacing applied here rather than via `columnFlow.Spacing` to avoid uneven gaps when some of the columns are hidden. column.Margin = new MarginPadding { Right = 10 }; diff --git a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs index b8f4b8a196..a292a50b72 100644 --- a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs @@ -1,14 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Game.Rulesets.Mods; using osu.Game.Utils; -using osuTK.Input; namespace osu.Game.Overlays.Mods { @@ -19,7 +15,7 @@ namespace osu.Game.Overlays.Mods { } - protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new UserModColumn(modType, false, toggleKeys); + protected override ModColumn CreateModColumn(ModType modType) => new UserModColumn(modType, false); protected override IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) { @@ -44,8 +40,8 @@ namespace osu.Game.Overlays.Mods private class UserModColumn : ModColumn { - public UserModColumn(ModType modType, bool allowBulkSelection, [CanBeNull] Key[] toggleKeys = null) - : base(modType, allowBulkSelection, toggleKeys) + public UserModColumn(ModType modType, bool allowBulkSelection) + : base(modType, allowBulkSelection) { } diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index c27b78642a..0f02692eda 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; -using osuTK.Input; namespace osu.Game.Screens.OnlinePlay { @@ -33,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay IsValidMod = _ => true; } - protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys); + protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true); protected override IEnumerable CreateFooterButtons() => base.CreateFooterButtons().Prepend( new SelectAllModsButton(this) From 816fd338cb8cd4143242053d415783ccee08e4fd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Jun 2022 19:57:27 +0900 Subject: [PATCH 080/363] Fix typo --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index d02f8fe6b2..cf9109eb8b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -387,7 +387,7 @@ namespace osu.Game.Database /// The work to run. public Task WriteAsync(Action action) { - // Regardless of calling Realm.GetInstance of Realm.GetInstanceAsync, there is a blocking overhead on retrieval. + // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval. // Adding a forced Task.Run resolves this. return Task.Run(async () => From 5abd8a07d2ba2dcea0ab6e0c80b8cca8cc714a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 13:03:46 +0200 Subject: [PATCH 081/363] Add setting for changing mod select hotkey style --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ osu.Game/Localisation/UserInterfaceStrings.cs | 5 ++++ .../Mods/Input/ModSelectHotkeyStyle.cs | 27 +++++++++++++++++++ .../UserInterface/SongSelectSettings.cs | 7 +++++ 4 files changed, 42 insertions(+) create mode 100644 osu.Game/Overlays/Mods/Input/ModSelectHotkeyStyle.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a0e1d9ddc4..713166a9a0 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -20,6 +20,7 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Overlays; +using osu.Game.Overlays.Mods.Input; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -47,6 +48,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.SongSelectSortingMode, SortMode.Title); SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); + SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential); SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f); @@ -324,6 +326,7 @@ namespace osu.Game.Configuration SongSelectGroupingMode, SongSelectSortingMode, RandomSelectAlgorithm, + ModSelectHotkeyStyle, ShowFpsDisplay, ChatDisplayHeight, BeatmapListingCardSize, diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 4e7af99ce9..a007f760d8 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -106,6 +106,11 @@ namespace osu.Game.Localisation /// public static LocalisableString RandomSelectionAlgorithm => new TranslatableString(getKey(@"random_selection_algorithm"), @"Random selection algorithm"); + /// + /// "Mod select hotkey style" + /// + public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); + /// /// "no limit" /// diff --git a/osu.Game/Overlays/Mods/Input/ModSelectHotkeyStyle.cs b/osu.Game/Overlays/Mods/Input/ModSelectHotkeyStyle.cs new file mode 100644 index 0000000000..6375b37f8b --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/ModSelectHotkeyStyle.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.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// The style of hotkey handling to use on the mod select screen. + /// + public enum ModSelectHotkeyStyle + { + /// + /// Each letter row on the keyboard controls one of the three first s. + /// Individual letters in a row trigger the mods in a sequential fashion. + /// Uses . + /// + Sequential, + + /// + /// Matches keybindings from stable 1:1. + /// One keybinding can toggle between what used to be s on stable, + /// and some mods in a column may not have any hotkeys at all. + /// + Classic + } +} diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 8cec7bbb30..507e116723 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Overlays.Mods.Input; namespace osu.Game.Overlays.Settings.Sections.UserInterface { @@ -61,6 +62,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface { LabelText = UserInterfaceStrings.RandomSelectionAlgorithm, Current = config.GetBindable(OsuSetting.RandomSelectAlgorithm), + }, + new SettingsEnumDropdown + { + LabelText = UserInterfaceStrings.ModSelectHotkeyStyle, + Current = config.GetBindable(OsuSetting.ModSelectHotkeyStyle), + ClassicDefault = ModSelectHotkeyStyle.Classic } }; } From 658f5341c7b6b8717ac82fb5dea1a17129b6ebad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 13:10:22 +0200 Subject: [PATCH 082/363] Set up flow for switching between hotkey styles --- .../Visual/UserInterface/TestSceneModColumn.cs | 9 ++++++++- .../Mods/Input/ClassicModHotkeyHandler.cs | 16 ++++++++++++++++ osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs | 6 ++++-- osu.Game/Overlays/Mods/ModColumn.cs | 10 +++++++--- 4 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index f98f39d445..d992f1aa05 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -9,9 +9,11 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Mods.Input; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Utils; @@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + [TestCase(ModType.DifficultyReduction)] [TestCase(ModType.DifficultyIncrease)] [TestCase(ModType.Conversion)] @@ -132,8 +137,10 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestKeyboardSelection() + public void TestSequentialKeyboardSelection() { + AddStep("set sequential hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential)); + ModColumn column = null!; AddStep("create content", () => Child = new Container { diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs new file mode 100644 index 0000000000..1dd732b79c --- /dev/null +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods.Input +{ + /// + /// Uses bindings from stable 1:1. + /// + public class ClassicModHotkeyHandler : IModHotkeyHandler + { + public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) => false; // TODO + } +} diff --git a/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs index 22de557979..5c46b2065f 100644 --- a/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs @@ -13,14 +13,16 @@ namespace osu.Game.Overlays.Mods.Input /// /// Creates an appropriate for the given . /// - public static IModHotkeyHandler Create(ModType modType) + public static IModHotkeyHandler Create(ModType modType, ModSelectHotkeyStyle style) { switch (modType) { case ModType.DifficultyReduction: case ModType.DifficultyIncrease: case ModType.Automation: - return SequentialModHotkeyHandler.Create(modType); + return style == ModSelectHotkeyStyle.Sequential + ? (IModHotkeyHandler)SequentialModHotkeyHandler.Create(modType) + : new ClassicModHotkeyHandler(); default: return new NoopModHotkeyHandler(); diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 998437a0a0..cf3d354d47 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -70,7 +71,8 @@ namespace osu.Game.Overlays.Mods protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); - private readonly IModHotkeyHandler hotkeyHandler; + private Bindable hotkeyStyle = null!; + private IModHotkeyHandler hotkeyHandler = null!; private readonly TextFlowContainer headerText; private readonly Box headerBackground; @@ -89,7 +91,6 @@ namespace osu.Game.Overlays.Mods public ModColumn(ModType modType, bool allowBulkSelection) { ModType = modType; - hotkeyHandler = ModHotkeyHandler.Create(modType); Width = 320; RelativeSizeAxes = Axes.Y; @@ -231,7 +232,7 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours) + private void load(OverlayColourProvider colourProvider, OsuColour colours, OsuConfigManager configManager) { headerBackground.Colour = accentColour = colours.ForModType(ModType); @@ -243,6 +244,8 @@ namespace osu.Game.Overlays.Mods contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3); contentBackground.Colour = colourProvider.Background4; + + hotkeyStyle = configManager.GetBindable(OsuSetting.ModSelectHotkeyStyle); } protected override void LoadComplete() @@ -250,6 +253,7 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true); + hotkeyStyle.BindValueChanged(val => hotkeyHandler = ModHotkeyHandler.Create(ModType, val.NewValue), true); asyncLoadPanels(); } From 143c8e8da679b156868c40d7b72ffca2b011977b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 13:33:33 +0200 Subject: [PATCH 083/363] Add test scene for desired classic selection behaviour --- .../UserInterface/TestSceneModColumn.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index d992f1aa05..25e99ac24b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -178,6 +178,56 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("clear filter", () => setFilter(null)); } + [Test] + public void TestClassicKeyboardSelection() + { + AddStep("set classic hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Classic)); + + ModColumn column = null!; + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = column = new ModColumn(ModType.DifficultyIncrease, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AvailableMods = getExampleModsFor(ModType.DifficultyIncrease) + } + }); + + AddUntilStep("wait for panel load", () => column.IsLoaded && column.ItemsLoaded); + + AddStep("press A", () => InputManager.Key(Key.A)); + AddAssert("HR panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value); + + AddStep("press A again", () => InputManager.Key(Key.A)); + AddAssert("HR panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value); + + AddStep("press D", () => InputManager.Key(Key.D)); + AddAssert("DT panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + + AddStep("press D again", () => InputManager.Key(Key.D)); + AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press D again", () => InputManager.Key(Key.D)); + AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press Shift-D", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.D); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press J", () => InputManager.Key(Key.J)); + AddAssert("no change", () => this.ChildrenOfType().Single(panel => panel.Active.Value).Mod.Acronym == "NC"); + } + private void setFilter(Func? filter) { foreach (var modState in this.ChildrenOfType().Single().AvailableMods) From 234120ff43ec16c09f394067b8e7c5d67330135b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 13:37:17 +0200 Subject: [PATCH 084/363] Forward entire event to `IModHotkeyHandler` Required for shift handling in the classic implementation. --- .../Overlays/Mods/Input/ClassicModHotkeyHandler.cs | 4 ++-- osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs | 10 +++++----- osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs | 4 ++-- .../Overlays/Mods/Input/SequentialModHotkeyHandler.cs | 5 +++-- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index 1dd732b79c..e22ad13451 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osuTK.Input; +using osu.Framework.Input.Events; namespace osu.Game.Overlays.Mods.Input { @@ -11,6 +11,6 @@ namespace osu.Game.Overlays.Mods.Input /// public class ClassicModHotkeyHandler : IModHotkeyHandler { - public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) => false; // TODO + public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) => false; // TODO } } diff --git a/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs index aec16ff764..d2cc0e84d2 100644 --- a/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/IModHotkeyHandler.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osuTK.Input; +using osu.Framework.Input.Events; namespace osu.Game.Overlays.Mods.Input { @@ -12,11 +12,11 @@ namespace osu.Game.Overlays.Mods.Input public interface IModHotkeyHandler { /// - /// Attempt to handle a press of the supplied as a selection of one of the mods in . + /// Attempt to handle the supplied as a selection of one of the mods in . /// - /// The key that was pressed by the user. + /// The event representing the user's keypress. /// The list of currently available mods. - /// Whether the was handled as a mod selection/deselection. - bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods); + /// Whether the supplied event was handled as a mod selection/deselection. + bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods); } } diff --git a/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs index 81152226da..3f7a6298a1 100644 --- a/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/NoopModHotkeyHandler.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osuTK.Input; +using osu.Framework.Input.Events; namespace osu.Game.Overlays.Mods.Input { @@ -12,6 +12,6 @@ namespace osu.Game.Overlays.Mods.Input /// public class NoopModHotkeyHandler : IModHotkeyHandler { - public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) => false; + public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) => false; } } diff --git a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs index 45cfa60fff..e6717053e1 100644 --- a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; using osuTK.Input; @@ -41,9 +42,9 @@ namespace osu.Game.Overlays.Mods.Input toggleKeys = keys; } - public bool HandleHotkeyPressed(Key hotkey, IEnumerable availableMods) + public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) { - int index = Array.IndexOf(toggleKeys, hotkey); + int index = Array.IndexOf(toggleKeys, e.Key); if (index < 0) return false; diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index cf3d354d47..1fef7257b5 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -432,7 +432,7 @@ namespace osu.Game.Overlays.Mods if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.Repeat) return false; - return hotkeyHandler.HandleHotkeyPressed(e.Key, availableMods); + return hotkeyHandler.HandleHotkeyPressed(e, availableMods); } #endregion From 7b7b8c1892e36a9d2dda89c95e69d1f6d09ed4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 14:24:30 +0200 Subject: [PATCH 085/363] Implement behaviour for classic selection style --- .../Mods/Input/ClassicModHotkeyHandler.cs | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index e22ad13451..0faf509136 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -1,8 +1,13 @@ // 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.Diagnostics; +using System.Linq; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Mods; +using osuTK.Input; namespace osu.Game.Overlays.Mods.Input { @@ -11,6 +16,65 @@ namespace osu.Game.Overlays.Mods.Input /// public class ClassicModHotkeyHandler : IModHotkeyHandler { - public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) => false; // TODO + private static readonly Dictionary mod_type_lookup = new Dictionary + { + [Key.Q] = new[] { typeof(ModEasy) }, + [Key.W] = new[] { typeof(ModNoFail) }, + [Key.E] = new[] { typeof(ModHalfTime) }, + [Key.A] = new[] { typeof(ModHardRock) }, + [Key.S] = new[] { typeof(ModSuddenDeath), typeof(ModPerfect) }, + [Key.D] = new[] { typeof(ModDoubleTime), typeof(ModNightcore) }, + [Key.F] = new[] { typeof(ModHidden) }, + [Key.G] = new[] { typeof(ModFlashlight) }, + [Key.Z] = new[] { typeof(ModRelax) }, + [Key.V] = new[] { typeof(ModAutoplay), typeof(ModCinema) } + }; + + public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) + { + if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch)) + return false; + + var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch)).ToArray(); + + if (matchingMods.Length == 0) + return false; + + if (matchingMods.Length == 1) + { + matchingMods.Single().Active.Toggle(); + return true; + } + + // we're assuming that only one mod from the group can be active at a time. + // this is mostly ensured by `IncompatibleMods` definitions, but let's make sure just in case. + Debug.Assert(matchingMods.Count(modState => modState.Active.Value) <= 1); + int currentSelectedIndex = Array.FindIndex(matchingMods, modState => modState.Active.Value); + + // `FindIndex` will return -1 if it doesn't find the item. + // this is convenient in the forward direction, since if we add 1 then we'll end up at the first item, + // but less so in the backwards direction. + // for convenience, detect this situation and set the index to one index past the last item. + // this makes it so that if we subtract 1 then we'll end up at the last item again. + if (currentSelectedIndex < 0 && e.ShiftPressed) + currentSelectedIndex = matchingMods.Length; + + int indexToSelect = e.ShiftPressed ? currentSelectedIndex - 1 : currentSelectedIndex + 1; + + // `currentSelectedIndex` and `indexToSelect` can both be equal to -1 or `matchingMods.Length`. + // if the former is beyond array range, it means nothing was previously selected and so there's nothing to deselect. + // if the latter is beyond array range, it means that either the previous selection was first and we're going backwards, + // or it was last and we're going forwards. + // in either case there is nothing to select. + if (currentSelectedIndex >= 0 && currentSelectedIndex <= matchingMods.Length - 1) + matchingMods[currentSelectedIndex].Active.Value = false; + if (indexToSelect >= 0 && indexToSelect <= matchingMods.Length - 1) + matchingMods[indexToSelect].Active.Value = true; + + return true; + } + + private static bool matches(ModState modState, Type[] typesToMatch) + => typesToMatch.Any(typeToMatch => typeToMatch.IsInstanceOfType(modState.Mod)); } } From da1814e7c3bcc8beff26ec0de471a0ca19106a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 14:48:41 +0200 Subject: [PATCH 086/363] Restructure everything to fix free mod overlay issue --- .../UserInterface/TestSceneModColumn.cs | 55 +++++++++++++++++-- .../Mods/Input/ClassicModHotkeyHandler.cs | 22 +++++++- .../Overlays/Mods/Input/ModHotkeyHandler.cs | 32 ----------- osu.Game/Overlays/Mods/ModColumn.cs | 33 +++++++++-- .../Overlays/Mods/UserModSelectOverlay.cs | 4 +- 5 files changed, 101 insertions(+), 45 deletions(-) delete mode 100644 osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 25e99ac24b..ec3c5b6ac6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestClassicKeyboardSelection() + public void TestClassicKeyboardExclusiveSelection() { AddStep("set classic hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Classic)); @@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.UserInterface { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(30), - Child = column = new ModColumn(ModType.DifficultyIncrease, true) + Child = column = new ModColumn(ModType.DifficultyIncrease, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -228,6 +228,53 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("no change", () => this.ChildrenOfType().Single(panel => panel.Active.Value).Mod.Acronym == "NC"); } + [Test] + public void TestClassicKeyboardIncompatibleSelection() + { + AddStep("set classic hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Classic)); + + ModColumn column = null!; + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = column = new ModColumn(ModType.DifficultyIncrease, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AvailableMods = getExampleModsFor(ModType.DifficultyIncrease) + } + }); + + AddUntilStep("wait for panel load", () => column.IsLoaded && column.ItemsLoaded); + + AddStep("press A", () => InputManager.Key(Key.A)); + AddAssert("HR panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value); + + AddStep("press A again", () => InputManager.Key(Key.A)); + AddAssert("HR panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value); + + AddStep("press D", () => InputManager.Key(Key.D)); + AddAssert("DT panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press D again", () => InputManager.Key(Key.D)); + AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press Shift-D", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.D); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + AddAssert("DT panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value); + AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value); + + AddStep("press J", () => InputManager.Key(Key.J)); + AddAssert("no change", () => this.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + } + private void setFilter(Func? filter) { foreach (var modState in this.ChildrenOfType().Single().AvailableMods) @@ -238,8 +285,8 @@ namespace osu.Game.Tests.Visual.UserInterface { public new bool SelectionAnimationRunning => base.SelectionAnimationRunning; - public TestModColumn(ModType modType, bool allowBulkSelection) - : base(modType, allowBulkSelection) + public TestModColumn(ModType modType, bool allowIncompatibleSelection) + : base(modType, allowIncompatibleSelection) { } } diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index 0faf509136..33d86cbdaa 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -30,6 +30,13 @@ namespace osu.Game.Overlays.Mods.Input [Key.V] = new[] { typeof(ModAutoplay), typeof(ModCinema) } }; + private readonly bool allowIncompatibleSelection; + + public ClassicModHotkeyHandler(bool allowIncompatibleSelection) + { + this.allowIncompatibleSelection = allowIncompatibleSelection; + } + public bool HandleHotkeyPressed(KeyDownEvent e, IEnumerable availableMods) { if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch)) @@ -46,8 +53,19 @@ namespace osu.Game.Overlays.Mods.Input return true; } - // we're assuming that only one mod from the group can be active at a time. - // this is mostly ensured by `IncompatibleMods` definitions, but let's make sure just in case. + if (allowIncompatibleSelection) + { + // easier path - multiple incompatible mods can be active at a time. + // this is used in the free mod select overlay. + // in this case, just toggle everything. + bool anyActive = matchingMods.Any(mod => mod.Active.Value); + foreach (var mod in matchingMods) + mod.Active.Value = !anyActive; + return true; + } + + // we now know there are multiple possible mods to handle, and only one of them can be active at a time. + // let's make sure of this just in case. Debug.Assert(matchingMods.Count(modState => modState.Active.Value) <= 1); int currentSelectedIndex = Array.FindIndex(matchingMods, modState => modState.Active.Value); diff --git a/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs deleted file mode 100644 index 5c46b2065f..0000000000 --- a/osu.Game/Overlays/Mods/Input/ModHotkeyHandler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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.Rulesets.Mods; - -namespace osu.Game.Overlays.Mods.Input -{ - /// - /// Static factory class for s. - /// - public static class ModHotkeyHandler - { - /// - /// Creates an appropriate for the given . - /// - public static IModHotkeyHandler Create(ModType modType, ModSelectHotkeyStyle style) - { - switch (modType) - { - case ModType.DifficultyReduction: - case ModType.DifficultyIncrease: - case ModType.Automation: - return style == ModSelectHotkeyStyle.Sequential - ? (IModHotkeyHandler)SequentialModHotkeyHandler.Create(modType) - : new ClassicModHotkeyHandler(); - - default: - return new NoopModHotkeyHandler(); - } - } - } -} diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 1fef7257b5..f8f6b814ca 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -71,8 +71,7 @@ namespace osu.Game.Overlays.Mods protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); - private Bindable hotkeyStyle = null!; - private IModHotkeyHandler hotkeyHandler = null!; + private readonly bool allowIncompatibleSelection; private readonly TextFlowContainer headerText; private readonly Box headerBackground; @@ -83,14 +82,18 @@ namespace osu.Game.Overlays.Mods private Colour4 accentColour; + private Bindable hotkeyStyle = null!; + private IModHotkeyHandler hotkeyHandler = null!; + private Task? latestLoadTask; internal bool ItemsLoaded => latestLoadTask == null; private const float header_height = 42; - public ModColumn(ModType modType, bool allowBulkSelection) + public ModColumn(ModType modType, bool allowIncompatibleSelection) { ModType = modType; + this.allowIncompatibleSelection = allowIncompatibleSelection; Width = 320; RelativeSizeAxes = Axes.Y; @@ -198,7 +201,7 @@ namespace osu.Game.Overlays.Mods createHeaderText(); - if (allowBulkSelection) + if (allowIncompatibleSelection) { controlContainer.Height = 35; controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this) @@ -253,7 +256,7 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true); - hotkeyStyle.BindValueChanged(val => hotkeyHandler = ModHotkeyHandler.Create(ModType, val.NewValue), true); + hotkeyStyle.BindValueChanged(val => hotkeyHandler = CreateHotkeyHandler(val.NewValue), true); asyncLoadPanels(); } @@ -427,6 +430,26 @@ namespace osu.Game.Overlays.Mods #region Keyboard selection support + /// + /// Creates an appropriate for this column's and + /// the supplied . + /// + protected virtual IModHotkeyHandler CreateHotkeyHandler(ModSelectHotkeyStyle hotkeyStyle) + { + switch (ModType) + { + case ModType.DifficultyReduction: + case ModType.DifficultyIncrease: + case ModType.Automation: + return hotkeyStyle == ModSelectHotkeyStyle.Sequential + ? (IModHotkeyHandler)SequentialModHotkeyHandler.Create(ModType) + : new ClassicModHotkeyHandler(allowIncompatibleSelection); + + default: + return new NoopModHotkeyHandler(); + } + } + protected override bool OnKeyDown(KeyDownEvent e) { if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.Repeat) diff --git a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs index a292a50b72..1090306c5b 100644 --- a/osu.Game/Overlays/Mods/UserModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/UserModSelectOverlay.cs @@ -40,8 +40,8 @@ namespace osu.Game.Overlays.Mods private class UserModColumn : ModColumn { - public UserModColumn(ModType modType, bool allowBulkSelection) - : base(modType, allowBulkSelection) + public UserModColumn(ModType modType, bool allowIncompatibleSelection) + : base(modType, allowIncompatibleSelection) { } From a996325e19bf5496994802e0c8d88c081b164e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 15:00:39 +0200 Subject: [PATCH 087/363] Add test coverage for filter handling in classic style --- .../Visual/UserInterface/TestSceneModColumn.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index ec3c5b6ac6..3765bc2ca0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -226,6 +226,11 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press J", () => InputManager.Key(Key.J)); AddAssert("no change", () => this.ChildrenOfType().Single(panel => panel.Active.Value).Mod.Acronym == "NC"); + + AddStep("filter everything but NC", () => setFilter(mod => mod.Acronym == "NC")); + + AddStep("press A", () => InputManager.Key(Key.A)); + AddAssert("no change", () => this.ChildrenOfType().Single(panel => panel.Active.Value).Mod.Acronym == "NC"); } [Test] @@ -273,6 +278,11 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press J", () => InputManager.Key(Key.J)); AddAssert("no change", () => this.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + + AddStep("filter everything but NC", () => setFilter(mod => mod.Acronym == "NC")); + + AddStep("press A", () => InputManager.Key(Key.A)); + AddAssert("no change", () => this.ChildrenOfType().Count(panel => panel.Active.Value) == 2); } private void setFilter(Func? filter) From 9e5cc89edbcef90f2804e6a0eb163445b08f07a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 15:01:33 +0200 Subject: [PATCH 088/363] Fix classic hotkeys toggling filtered mods --- osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index 33d86cbdaa..4f3c18fc43 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Mods.Input if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch)) return false; - var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch)).ToArray(); + var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && !modState.Filtered.Value).ToArray(); if (matchingMods.Length == 0) return false; From a9f6eb0293fd8fcc018f4ff2d29dba10f24bc117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 15:18:37 +0200 Subject: [PATCH 089/363] Add test coverage for new intended behaviour of sequential hotkey style --- osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 3765bc2ca0..72cddc0ad2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -165,9 +165,12 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("set filter to NF", () => setFilter(mod => mod.Acronym == "NF")); AddStep("press W", () => InputManager.Key(Key.W)); + AddAssert("NF panel not selected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); + + AddStep("press Q", () => InputManager.Key(Key.Q)); AddAssert("NF panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); - AddStep("press W again", () => InputManager.Key(Key.W)); + AddStep("press Q again", () => InputManager.Key(Key.Q)); AddAssert("NF panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value); AddStep("filter out everything", () => setFilter(_ => false)); From f564ed589fdac206748c43000d98b2c918f1ae78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 15:19:41 +0200 Subject: [PATCH 090/363] Alter sequential hotkey style to always use visible index Previous behaviour was once mentioned off-hand as unintuitive. --- osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs index e6717053e1..dedb556304 100644 --- a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs @@ -48,8 +48,8 @@ namespace osu.Game.Overlays.Mods.Input if (index < 0) return false; - var modState = availableMods.ElementAtOrDefault(index); - if (modState == null || modState.Filtered.Value) + var modState = availableMods.Where(modState => !modState.Filtered.Value).ElementAtOrDefault(index); + if (modState == null) return false; modState.Active.Toggle(); From 7013909322687424052ffe6562a0b26d4ca62a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 21 Jun 2022 16:43:04 +0200 Subject: [PATCH 091/363] Remove unnecessary `protected virtual` --- osu.Game/Overlays/Mods/ModColumn.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index f8f6b814ca..c51e6baa0d 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -256,7 +256,7 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); toggleAllCheckbox?.Current.BindValueChanged(_ => updateToggleAllText(), true); - hotkeyStyle.BindValueChanged(val => hotkeyHandler = CreateHotkeyHandler(val.NewValue), true); + hotkeyStyle.BindValueChanged(val => hotkeyHandler = createHotkeyHandler(val.NewValue), true); asyncLoadPanels(); } @@ -434,7 +434,7 @@ namespace osu.Game.Overlays.Mods /// Creates an appropriate for this column's and /// the supplied . /// - protected virtual IModHotkeyHandler CreateHotkeyHandler(ModSelectHotkeyStyle hotkeyStyle) + private IModHotkeyHandler createHotkeyHandler(ModSelectHotkeyStyle hotkeyStyle) { switch (ModType) { From e4931f977cadd7d90a4f965d4215013eaa207fe6 Mon Sep 17 00:00:00 2001 From: Supersonicboss1 Date: Tue, 21 Jun 2022 23:00:03 +0100 Subject: [PATCH 092/363] improved UI of maintenance section in settings --- ...{GeneralSettings.cs => BeatmapSettings.cs} | 88 +++---------------- .../Maintenance/CollectionsSettings.cs | 49 +++++++++++ .../Sections/Maintenance/ScoreSettings.cs | 52 +++++++++++ .../Sections/Maintenance/SkinSettings.cs | 52 +++++++++++ .../Settings/Sections/MaintenanceSection.cs | 5 +- 5 files changed, 170 insertions(+), 76 deletions(-) rename osu.Game/Overlays/Settings/Sections/Maintenance/{GeneralSettings.cs => BeatmapSettings.cs} (62%) create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs similarity index 62% rename from osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs rename to osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 916c607c1c..474f23e4e7 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -9,31 +9,25 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osu.Game.Collections; using osu.Game.Database; using osu.Game.Localisation; using osu.Game.Scoring; -using osu.Game.Skinning; namespace osu.Game.Overlays.Settings.Sections.Maintenance { - public class GeneralSettings : SettingsSubsection + public class BeatmapSettings : SettingsSubsection { - protected override LocalisableString Header => "General"; - + protected override LocalisableString Header => "Beatmaps"; private SettingsButton importBeatmapsButton; private SettingsButton importScoresButton; - private SettingsButton importSkinsButton; - private SettingsButton importCollectionsButton; private SettingsButton deleteBeatmapsButton; + private SettingsButton deleteBeatmapVideosButton; private SettingsButton deleteScoresButton; - private SettingsButton deleteSkinsButton; private SettingsButton restoreButton; private SettingsButton undeleteButton; - private SettingsButton deleteBeatmapVideosButton; [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, ScoreManager scores, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) { if (legacyImportManager?.SupportsImportFromStable == true) { @@ -46,6 +40,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }); + Add(importScoresButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.ImportScoresFromStable, + Action = () => + { + importScoresButton.Enabled.Value = false; + legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); + } + }); } Add(deleteBeatmapsButton = new DangerousSettingsButton @@ -74,19 +77,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }); - if (legacyImportManager?.SupportsImportFromStable == true) - { - Add(importScoresButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.ImportScoresFromStable, - Action = () => - { - importScoresButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); - } - }); - } - Add(deleteScoresButton = new DangerousSettingsButton { Text = MaintenanceSettingsStrings.DeleteAllScores, @@ -99,58 +89,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance })); } }); - - if (legacyImportManager?.SupportsImportFromStable == true) - { - Add(importSkinsButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.ImportSkinsFromStable, - Action = () => - { - importSkinsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); - } - }); - } - - Add(deleteSkinsButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllSkins, - Action = () => - { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => - { - deleteSkinsButton.Enabled.Value = false; - Task.Run(() => skins.Delete()).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); - })); - } - }); - - if (collectionManager != null) - { - if (legacyImportManager?.SupportsImportFromStable == true) - { - Add(importCollectionsButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.ImportCollectionsFromStable, - Action = () => - { - importCollectionsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); - } - }); - } - - Add(new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllCollections, - Action = () => - { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll)); - } - }); - } - AddRange(new Drawable[] { restoreButton = new SettingsButton diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs new file mode 100644 index 0000000000..98a9a271ab --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Localisation; +using osu.Game.Collections; +using osu.Game.Database; +using osu.Game.Localisation; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class CollectionsSettings : SettingsSubsection + { + protected override LocalisableString Header => "Collections"; + private SettingsButton importCollectionsButton; + + [BackgroundDependencyLoader(permitNulls: true)] + private void load([CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + { + if (collectionManager != null) + { + if (legacyImportManager?.SupportsImportFromStable == true) + { + Add(importCollectionsButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.ImportCollectionsFromStable, + Action = () => + { + importCollectionsButton.Enabled.Value = false; + legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + } + }); + } + + Add(new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllCollections, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll)); + } + }); + } + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs new file mode 100644 index 0000000000..ddeb48725c --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Localisation; +using osu.Game.Database; +using osu.Game.Localisation; +using osu.Game.Scoring; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class ScoreSettings : SettingsSubsection + { + protected override LocalisableString Header => "Scores"; + private SettingsButton importScoresButton; + private SettingsButton deleteScoresButton; + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(ScoreManager scores, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + { + if (legacyImportManager?.SupportsImportFromStable == true) + { + Add(importScoresButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.ImportScoresFromStable, + Action = () => + { + importScoresButton.Enabled.Value = false; + legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); + } + }); + } + + Add(deleteScoresButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllScores, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => + { + deleteScoresButton.Enabled.Value = false; + Task.Run(() => scores.Delete()).ContinueWith(t => Schedule(() => deleteScoresButton.Enabled.Value = true)); + })); + } + }); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs new file mode 100644 index 0000000000..6019023c50 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Localisation; +using osu.Game.Database; +using osu.Game.Localisation; +using osu.Game.Skinning; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class SkinSettings : SettingsSubsection + { + protected override LocalisableString Header => "Skins"; + private SettingsButton importSkinsButton; + private SettingsButton deleteSkinsButton; + + [BackgroundDependencyLoader(permitNulls: true)] + private void load(SkinManager skins, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + { + if (legacyImportManager?.SupportsImportFromStable == true) + { + Add(importSkinsButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.ImportSkinsFromStable, + Action = () => + { + importSkinsButton.Enabled.Value = false; + legacyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); + } + }); + } + + Add(deleteSkinsButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllSkins, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => + { + deleteSkinsButton.Enabled.Value = false; + Task.Run(() => skins.Delete()).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); + })); + } + }); + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index cfe1961bc1..19d02baa4a 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -24,7 +24,10 @@ namespace osu.Game.Overlays.Settings.Sections { Children = new Drawable[] { - new GeneralSettings() + new BeatmapSettings(), + new SkinSettings(), + new CollectionsSettings(), + new ScoreSettings() }; } } From 4aa86b0a8001fa1e0f2f6895f7160d226b80cb6d Mon Sep 17 00:00:00 2001 From: Supersonicboss1 Date: Tue, 21 Jun 2022 23:41:25 +0100 Subject: [PATCH 093/363] fixed duplicates and cleaned up code --- .../Sections/Maintenance/BeatmapSettings.cs | 113 +++++++----------- .../Maintenance/CollectionsSettings.cs | 36 +++--- .../Sections/Maintenance/ScoreSettings.cs | 2 +- .../Sections/Maintenance/SkinSettings.cs | 2 +- 4 files changed, 64 insertions(+), 89 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 474f23e4e7..57de259670 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable @@ -11,7 +11,6 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Localisation; -using osu.Game.Scoring; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -19,15 +18,13 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { protected override LocalisableString Header => "Beatmaps"; private SettingsButton importBeatmapsButton; - private SettingsButton importScoresButton; private SettingsButton deleteBeatmapsButton; private SettingsButton deleteBeatmapVideosButton; - private SettingsButton deleteScoresButton; private SettingsButton restoreButton; private SettingsButton undeleteButton; [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, ScoreManager scores, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private void load(BeatmapManager beatmaps, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) { if (legacyImportManager?.SupportsImportFromStable == true) { @@ -40,76 +37,54 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }); - Add(importScoresButton = new SettingsButton + + Add(deleteBeatmapsButton = new DangerousSettingsButton { - Text = MaintenanceSettingsStrings.ImportScoresFromStable, + Text = MaintenanceSettingsStrings.DeleteAllBeatmaps, Action = () => { - importScoresButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); + dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => + { + deleteBeatmapsButton.Enabled.Value = false; + Task.Run(() => beatmaps.Delete()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); + })); + } + }); + + Add(deleteBeatmapVideosButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos, + Action = () => + { + dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => + { + deleteBeatmapVideosButton.Enabled.Value = false; + Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); + })); + } + }); + AddRange(new Drawable[] + { + restoreButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.RestoreAllHiddenDifficulties, + Action = () => + { + restoreButton.Enabled.Value = false; + Task.Run(beatmaps.RestoreAll).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); + } + }, + undeleteButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.RestoreAllRecentlyDeletedBeatmaps, + Action = () => + { + undeleteButton.Enabled.Value = false; + Task.Run(beatmaps.UndeleteAll).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + } } }); } - - Add(deleteBeatmapsButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllBeatmaps, - Action = () => - { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => - { - deleteBeatmapsButton.Enabled.Value = false; - Task.Run(() => beatmaps.Delete()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); - })); - } - }); - - Add(deleteBeatmapVideosButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos, - Action = () => - { - dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => - { - deleteBeatmapVideosButton.Enabled.Value = false; - Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); - })); - } - }); - - Add(deleteScoresButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllScores, - Action = () => - { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => - { - deleteScoresButton.Enabled.Value = false; - Task.Run(() => scores.Delete()).ContinueWith(t => Schedule(() => deleteScoresButton.Enabled.Value = true)); - })); - } - }); - AddRange(new Drawable[] - { - restoreButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.RestoreAllHiddenDifficulties, - Action = () => - { - restoreButton.Enabled.Value = false; - Task.Run(beatmaps.RestoreAll).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); - } - }, - undeleteButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.RestoreAllRecentlyDeletedBeatmaps, - Action = () => - { - undeleteButton.Enabled.Value = false; - Task.Run(beatmaps.UndeleteAll).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); - } - }, - }); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 98a9a271ab..31b889ce0e 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable @@ -20,30 +20,30 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [BackgroundDependencyLoader(permitNulls: true)] private void load([CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) { - if (collectionManager != null) - { - if (legacyImportManager?.SupportsImportFromStable == true) - { - Add(importCollectionsButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.ImportCollectionsFromStable, - Action = () => - { - importCollectionsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); - } - }); - } + if (collectionManager == null) return; - Add(new DangerousSettingsButton + if (legacyImportManager?.SupportsImportFromStable == true) + + { + Add(importCollectionsButton = new SettingsButton { - Text = MaintenanceSettingsStrings.DeleteAllCollections, + Text = MaintenanceSettingsStrings.ImportCollectionsFromStable, Action = () => { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll)); + importCollectionsButton.Enabled.Value = false; + legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); } }); } + + Add(new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllCollections, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll)); + } + }); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs index ddeb48725c..26d9d2aa51 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs index 6019023c50..ad680b363e 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable From 50728f3fbc29a4e8fd9a39817b924468b50349ad Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 21 Jun 2022 18:31:09 -0700 Subject: [PATCH 094/363] Fix overlay header tab item not working with localisable string --- osu.Game/Overlays/TabControlOverlayHeader.cs | 28 +++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index 4e32afb86f..2b47e6bc46 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -8,10 +8,12 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -108,17 +110,25 @@ namespace osu.Game.Overlays public OverlayHeaderTabItem(T value) : base(value) { - if (!(Value is Enum enumValue)) - Text.Text = Value.ToString().ToLower(); - else + switch (Value) { - var localisableDescription = enumValue.GetLocalisableDescription(); - string nonLocalisableDescription = enumValue.GetDescription(); + case LocalisableString localisableString: + Text.Text = localisableString.ToLower(); + break; - // If localisable == non-localisable, then we must have a basic string, so .ToLower() is used. - Text.Text = localisableDescription.Equals(nonLocalisableDescription) - ? nonLocalisableDescription.ToLower() - : localisableDescription; + case Enum enumValue: + var localisableDescription = enumValue.GetLocalisableDescription(); + string nonLocalisableDescription = enumValue.GetDescription(); + + // If localisable == non-localisable, then we must have a basic string, so .ToLower() is used. + Text.Text = localisableDescription.Equals(nonLocalisableDescription) + ? nonLocalisableDescription.ToLower() + : localisableDescription; + break; + + default: + Text.Text = Value.ToString().ToLower(); + break; } Text.Font = OsuFont.GetFont(size: 14); From 07e0cd53a4c81d1256c21864ec71bc849ebf37d9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 21 Jun 2022 19:06:08 -0700 Subject: [PATCH 095/363] Add back online view container to chat overlay --- osu.Game/Overlays/ChatOverlay.cs | 74 +++++++++++++++++++------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 81a408598b..c491c0c695 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -18,6 +18,7 @@ using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Online; using osu.Game.Online.Chat; using osu.Game.Overlays.Chat; using osu.Game.Overlays.Chat.ChannelList; @@ -100,23 +101,10 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.X, Height = top_bar_height, }, - channelList = new ChannelList - { - RelativeSizeAxes = Axes.Y, - Width = side_bar_width, - Padding = new MarginPadding { Top = top_bar_height }, - }, new Container { RelativeSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Padding = new MarginPadding - { - Top = top_bar_height, - Left = side_bar_width, - Bottom = chat_bar_height, - }, + Padding = new MarginPadding { Top = top_bar_height }, Children = new Drawable[] { new Box @@ -124,24 +112,50 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background4, }, - currentChannelContainer = new Container + new OnlineViewContainer("Sign in to chat") { RelativeSizeAxes = Axes.Both, - }, - loading = new LoadingLayer(true), - channelListing = new ChannelListing - { - RelativeSizeAxes = Axes.Both, - }, - }, - }, - textBar = new ChatTextBar - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Padding = new MarginPadding { Left = side_bar_width }, - }, + Children = new Drawable[] + { + channelList = new ChannelList + { + RelativeSizeAxes = Axes.Y, + Width = side_bar_width, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Padding = new MarginPadding + { + Left = side_bar_width, + Bottom = chat_bar_height, + }, + Children = new Drawable[] + { + currentChannelContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + loading = new LoadingLayer(true), + channelListing = new ChannelListing + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + textBar = new ChatTextBar + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Padding = new MarginPadding { Left = side_bar_width }, + }, + } + } + } + } }; } From 23254d2ff206146b29fcf36c6643a404b1b33bbd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 22 Jun 2022 13:02:33 +0900 Subject: [PATCH 096/363] Fix broken files --- .../Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs | 2 +- .../Settings/Sections/Maintenance/CollectionsSettings.cs | 2 +- .../Overlays/Settings/Sections/Maintenance/ScoreSettings.cs | 2 +- osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 57de259670..7d5c8a9958 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 31b889ce0e..96583a1e4b 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs index 26d9d2aa51..ddeb48725c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs index ad680b363e..6019023c50 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd .Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable From 1018c9852d0ce5e7b6bf3b0f4e056ea92e5d8664 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 22 Jun 2022 13:07:44 +0900 Subject: [PATCH 097/363] Cleanup nullability --- .../Sections/Maintenance/BeatmapSettings.cs | 18 ++++++++---------- .../Maintenance/CollectionsSettings.cs | 10 ++++------ .../Sections/Maintenance/ScoreSettings.cs | 12 +++++------- .../Sections/Maintenance/SkinSettings.cs | 12 +++++------- 4 files changed, 22 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 7d5c8a9958..8039ee7cd9 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; @@ -17,14 +14,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public class BeatmapSettings : SettingsSubsection { protected override LocalisableString Header => "Beatmaps"; - private SettingsButton importBeatmapsButton; - private SettingsButton deleteBeatmapsButton; - private SettingsButton deleteBeatmapVideosButton; - private SettingsButton restoreButton; - private SettingsButton undeleteButton; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager beatmaps, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private SettingsButton importBeatmapsButton = null!; + private SettingsButton deleteBeatmapsButton = null!; + private SettingsButton deleteBeatmapVideosButton = null!; + private SettingsButton restoreButton = null!; + private SettingsButton undeleteButton = null!; + + [BackgroundDependencyLoader] + private void load(BeatmapManager beatmaps, LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) { if (legacyImportManager?.SupportsImportFromStable == true) { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 96583a1e4b..8387582fcb 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Game.Collections; @@ -15,10 +12,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public class CollectionsSettings : SettingsSubsection { protected override LocalisableString Header => "Collections"; - private SettingsButton importCollectionsButton; - [BackgroundDependencyLoader(permitNulls: true)] - private void load([CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private SettingsButton importCollectionsButton = null!; + + [BackgroundDependencyLoader] + private void load(CollectionManager? collectionManager, LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) { if (collectionManager == null) return; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs index ddeb48725c..9c1281f371 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Game.Database; @@ -16,11 +13,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public class ScoreSettings : SettingsSubsection { protected override LocalisableString Header => "Scores"; - private SettingsButton importScoresButton; - private SettingsButton deleteScoresButton; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(ScoreManager scores, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private SettingsButton importScoresButton = null!; + private SettingsButton deleteScoresButton = null!; + + [BackgroundDependencyLoader] + private void load(ScoreManager scores, LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) { if (legacyImportManager?.SupportsImportFromStable == true) { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs index 6019023c50..0fff9fef26 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Game.Database; @@ -16,11 +13,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance public class SkinSettings : SettingsSubsection { protected override LocalisableString Header => "Skins"; - private SettingsButton importSkinsButton; - private SettingsButton deleteSkinsButton; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(SkinManager skins, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay) + private SettingsButton importSkinsButton = null!; + private SettingsButton deleteSkinsButton = null!; + + [BackgroundDependencyLoader] + private void load(SkinManager skins, LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) { if (legacyImportManager?.SupportsImportFromStable == true) { From 321920bc857b07f1359ac0407a080c56f2815a6c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 22 Jun 2022 13:08:14 +0900 Subject: [PATCH 098/363] Remove one more nullable disable --- osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index 19d02baa4a..841a7d0abe 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; From ff440fc1a4517d1da519f2ace93aa75a0698c366 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 22 Jun 2022 13:13:04 +0900 Subject: [PATCH 099/363] Fix too many buttons inside condition --- .../Sections/Maintenance/BeatmapSettings.cs | 94 +++++++++---------- .../Maintenance/CollectionsSettings.cs | 1 - 2 files changed, 47 insertions(+), 48 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 8039ee7cd9..8c8c2c96ff 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -35,54 +35,54 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }); - - Add(deleteBeatmapsButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllBeatmaps, - Action = () => - { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => - { - deleteBeatmapsButton.Enabled.Value = false; - Task.Run(() => beatmaps.Delete()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); - })); - } - }); - - Add(deleteBeatmapVideosButton = new DangerousSettingsButton - { - Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos, - Action = () => - { - dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => - { - deleteBeatmapVideosButton.Enabled.Value = false; - Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); - })); - } - }); - AddRange(new Drawable[] - { - restoreButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.RestoreAllHiddenDifficulties, - Action = () => - { - restoreButton.Enabled.Value = false; - Task.Run(beatmaps.RestoreAll).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); - } - }, - undeleteButton = new SettingsButton - { - Text = MaintenanceSettingsStrings.RestoreAllRecentlyDeletedBeatmaps, - Action = () => - { - undeleteButton.Enabled.Value = false; - Task.Run(beatmaps.UndeleteAll).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); - } - } - }); } + + Add(deleteBeatmapsButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllBeatmaps, + Action = () => + { + dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => + { + deleteBeatmapsButton.Enabled.Value = false; + Task.Run(() => beatmaps.Delete()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); + })); + } + }); + + Add(deleteBeatmapVideosButton = new DangerousSettingsButton + { + Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos, + Action = () => + { + dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => + { + deleteBeatmapVideosButton.Enabled.Value = false; + Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); + })); + } + }); + AddRange(new Drawable[] + { + restoreButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.RestoreAllHiddenDifficulties, + Action = () => + { + restoreButton.Enabled.Value = false; + Task.Run(beatmaps.RestoreAll).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); + } + }, + undeleteButton = new SettingsButton + { + Text = MaintenanceSettingsStrings.RestoreAllRecentlyDeletedBeatmaps, + Action = () => + { + undeleteButton.Enabled.Value = false; + Task.Run(beatmaps.UndeleteAll).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + } + } + }); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 8387582fcb..74e9ebc084 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -21,7 +21,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance if (collectionManager == null) return; if (legacyImportManager?.SupportsImportFromStable == true) - { Add(importCollectionsButton = new SettingsButton { From f561d5b4ad5a96d88690c14126efe41ee3e946eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jun 2022 14:32:42 +0900 Subject: [PATCH 100/363] Change always-discarded-parameter inspection to hint See https://github.com/ppy/osu/blob/f8830c6850128456266c82de83273204f8b74ac0/osu.Game/Rulesets/Scoring/ScoreProcessor.cs#L518 Coming up as a suggestion (the only one in the solution). --- osu.sln.DotSettings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 286a1eb29f..e732b25951 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -126,6 +126,8 @@ HINT HINT WARNING + HINT + HINT HINT HINT HINT From 950551f4fda91db3ab8a078b29d2f4977fd3d370 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 22 Jun 2022 15:33:42 +0900 Subject: [PATCH 101/363] Fix metronome arm being stuck white when paused 'close enough' to center --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 37fc38404f..3895959982 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -276,6 +276,7 @@ namespace osu.Game.Screens.Edit.Timing if (Precision.AlmostEquals(swing.Rotation, 0, 1)) { swing.RotateTo(0, 60, Easing.OutQuint); + stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); sampleLatch?.Play(); return; } From 948c28f41532bd3b8406ce371cb453bdf1dbaca1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Jun 2022 20:34:05 +0900 Subject: [PATCH 102/363] Fix collection modified during `BlockAllOperations` if any subscriptions have been established --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index cf9109eb8b..68f9dcb541 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -822,7 +822,7 @@ namespace osu.Game.Database // Before disposing the update context, clean up all subscriptions. // Note that in the case of realm notification subscriptions, this is not really required (they will be cleaned up by disposal). // In the case of custom subscriptions, we want them to fire before the update realm is disposed in case they do any follow-up work. - foreach (var action in customSubscriptionsResetMap) + foreach (var action in customSubscriptionsResetMap.ToArray()) { action.Value?.Dispose(); customSubscriptionsResetMap[action.Key] = null; From bf5a7e3f2aa077fcbddaea82e510b74975bb2973 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 22 Jun 2022 17:01:28 -0400 Subject: [PATCH 103/363] add locked room filter --- .../Lounge/Components/FilterCriteria.cs | 1 + .../Lounge/Components/RoomsContainer.cs | 2 ++ .../OnlinePlay/Lounge/LoungeSubScreen.cs | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index 67e805986a..45c1b9b394 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -13,5 +13,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public RoomStatusFilter Status; public string Category; public RulesetInfo Ruleset; + public bool Locked = true; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 2c3a2997cc..908505e386 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,6 +87,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } + matchingFilter &= criteria.Locked || !r.Room.HasPassword.Value; + r.MatchingFilter = matchingFilter; } }); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index f7f3c27ede..a726ff632f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -85,6 +85,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private RoomsContainer roomsContainer; private SearchTextBox searchTextBox; private Dropdown statusDropdown; + private Checkbox lockedCheckbox; [BackgroundDependencyLoader(true)] private void load([CanBeNull] IdleTracker idleTracker) @@ -224,9 +225,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { SearchString = searchTextBox.Current.Value, Ruleset = ruleset.Value, - Status = statusDropdown.Current.Value + Status = statusDropdown.Current.Value, + Locked = lockedCheckbox.Current.Value, }; + #endregion + protected virtual IEnumerable CreateFilterControls() { statusDropdown = new SlimEnumDropdown @@ -237,10 +241,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge statusDropdown.Current.BindValueChanged(_ => UpdateFilter()); - yield return statusDropdown; - } + lockedCheckbox = new OsuTabControlCheckbox + { + Current = new Bindable(true), + RelativeSizeAxes = Axes.None, + Text = @"Show Locked", + }; - #endregion + lockedCheckbox.Current.BindValueChanged(_ => UpdateFilter()); + + return new Drawable[] { lockedCheckbox, statusDropdown, }; + } public override void OnEntering(ScreenTransitionEvent e) { From a03cfbc2d4ec60c07c3ea2156dad084a7e123247 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 22 Jun 2022 17:01:42 -0400 Subject: [PATCH 104/363] add test coverage --- .../TestSceneLoungeRoomsContainer.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 171f8eea52..363a2b4a4d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -157,6 +157,26 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); } + [Test] + public void TestLockedFiltering() + { + AddStep("add rooms", () => + { + RoomManager.AddRooms(1, withPassword: true); + RoomManager.AddRooms(1, withPassword: false); + }); + + AddUntilStep("both rooms shown", () => container.Rooms.Count(r => r.IsPresent) == 2); + + AddStep("filter locked rooms", () => container.Filter.Value = new FilterCriteria { Locked = false }); + + AddUntilStep("locked room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); + + AddStep("unfilter locked rooms", () => container.Filter.SetDefault()); + + AddUntilStep("both rooms shown", () => container.Rooms.Count(r => r.IsPresent) == 2); + } + [Test] public void TestPasswordProtectedRooms() { From bc557bacf05199aecd51adec69536eb1cdb10a9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 14:10:17 +0900 Subject: [PATCH 105/363] Remove room null assertion in `TestMultiplayerClient.ChangeState` to avoid assert This method is run from async contexts, but `get_Room` asserts update thread which is not the case. Was causing silent test failures (showing as pass but failing `dotnet-test` via return code): TestSceneMultiplayer.TestGameplayFlow ``` [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: Score preparation failed! [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: NUnit.Framework.AssertionException: : [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at osu.Framework.Logging.ThrowingTraceListener.Fail(String message1, String message2) [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at System.Diagnostics.TraceInternal.Fail(String message, String detailMessage) [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at System.Diagnostics.TraceInternal.TraceProvider.Fail(String message, String detailMessage) [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at System.Diagnostics.Debug.Fail(String message, String detailMessage) [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at osu.Game.Online.Multiplayer.MultiplayerClient.get_Room() in /opt/buildagent/work/ecd860037212ac52/osu.Game/Online/Multiplayer/MultiplayerClient.cs:line 98 [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at osu.Game.Tests.Visual.Multiplayer.TestMultiplayerClient.ChangeState(MultiplayerUserState newState) in /opt/buildagent/work/ecd860037212ac52/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs:line 275 [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at osu.Game.Screens.OnlinePlay.Multiplayer.MultiplayerPlayer.PrepareScoreForResultsAsync(Score score) in /opt/buildagent/work/ecd860037212ac52/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs:line 223 [13:13:17] : [dotnet test] [runtime] 2022-06-22 13:13:16 [error]: at osu.Game.Screens.Play.Player.prepareScoreForResults() in /opt/buildagent/work/ecd860037212ac52/osu.Game/Screens/Play/Player.cs:line 747 ``` --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index e5a1236abf..f2024a3107 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -272,8 +272,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task ChangeState(MultiplayerUserState newState) { - Debug.Assert(Room != null); - if (newState == MultiplayerUserState.Idle && LocalUser?.State == MultiplayerUserState.WaitingForLoad) return Task.CompletedTask; From ed9c55a7765d8e9771d6dd3b6ceca350c5781a3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 14:33:07 +0900 Subject: [PATCH 106/363] Ban usage of `ManualResetEventSlim.Wait()` without a timeout value --- CodeAnalysis/BannedSymbols.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index b72df0a306..77197c4c51 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -16,3 +16,4 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead. M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks. +M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever. From 7ef8b7df5f788427d2d0c0309923babb073b23bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 14:33:20 +0900 Subject: [PATCH 107/363] Add timeout for all `ManualResetEventSlim.Wait` invocations Timeout values were taken as best-guesses of upper values we'd expect from sane execution. --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 4 ++-- osu.Game.Tests/Database/GeneralUsageTests.cs | 4 ++-- .../Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs | 2 +- osu.Game/Database/DatabaseContextFactory.cs | 2 +- osu.Game/Graphics/ScreenshotManager.cs | 2 +- osu.Game/OsuGameBase.cs | 3 ++- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index bdc24315bf..b26fc3fd59 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -76,7 +76,7 @@ namespace osu.Game.Benchmarks } }); - done.Wait(); + done.Wait(60000); } [Benchmark] @@ -115,7 +115,7 @@ namespace osu.Game.Benchmarks } }); - done.Wait(); + done.Wait(60000); } [Benchmark] diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index d10ab2ad2b..c28a0c63af 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -79,11 +79,11 @@ namespace osu.Game.Tests.Database { hasThreadedUsage.Set(); - stopThreadedUsage.Wait(); + stopThreadedUsage.Wait(60000); }); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler); - hasThreadedUsage.Wait(); + hasThreadedUsage.Wait(60000); Assert.Throws(() => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs index fc3079cba0..5b4e0a88aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Task.Run(() => { - allowResponseCallback.Wait(); + allowResponseCallback.Wait(10000); allowResponseCallback.Reset(); Schedule(() => d?.Invoke("Incorrect password")); }); diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 8af649fbfa..075383c6e1 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -209,6 +209,6 @@ namespace osu.Game.Database public void SetMigrationCompletion() => migrationComplete.Set(); - public void WaitForMigrationCompletion() => migrationComplete.Wait(); + public void WaitForMigrationCompletion() => migrationComplete.Wait(300000); } } diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 7147f89dd0..fe9865dad6 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -103,7 +103,7 @@ namespace osu.Game.Graphics framesWaitedEvent.Set(); }, 10, true); - framesWaitedEvent.Wait(); + framesWaitedEvent.Wait(1000); waitDelegate.Cancel(); } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 58929ce4a4..7bbad3bb72 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -451,7 +451,8 @@ namespace osu.Game readyToRun.Set(); }, false); - readyToRun.Wait(); + if (!readyToRun.Wait(30000)) + throw new TimeoutException("Attempting to block for migration took too long."); bool? cleanupSucceded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); From 51268d0cc8333bb7f74e74e6fc776b698ea47d63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 15:28:20 +0900 Subject: [PATCH 108/363] Throw on `Wait` failure in a few remaining cases --- osu.Game/Database/DatabaseContextFactory.cs | 6 +++++- osu.Game/Graphics/ScreenshotManager.cs | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 075383c6e1..af91fb4971 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -209,6 +209,10 @@ namespace osu.Game.Database public void SetMigrationCompletion() => migrationComplete.Set(); - public void WaitForMigrationCompletion() => migrationComplete.Wait(300000); + public void WaitForMigrationCompletion() + { + if (!migrationComplete.Wait(300000)) + throw new TimeoutException("Migration took too long (likely stuck)."); + } } } diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index fe9865dad6..2cc9e63c87 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -103,7 +103,9 @@ namespace osu.Game.Graphics framesWaitedEvent.Set(); }, 10, true); - framesWaitedEvent.Wait(1000); + if (!framesWaitedEvent.Wait(1000)) + throw new TimeoutException("Screenshot data did not arrive in a timely fashion"); + waitDelegate.Cancel(); } } From 9be13f88fd3711bddce59ec63522e0aff22b499b Mon Sep 17 00:00:00 2001 From: 63411 <62799417+molneya@users.noreply.github.com> Date: Thu, 23 Jun 2022 15:51:28 +0800 Subject: [PATCH 109/363] revert customAccuracy changes --- .../Difficulty/ManiaPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 216dfe6c47..a925e7c0ac 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -73,6 +73,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty /// /// Accuracy used to weight judgements independently from the score's actual accuracy. /// - private double customAccuracy => Math.Max((countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50 - countMiss * 100) / (totalHits * 320), 0); + private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320); } } From 158f0a1f233d14234ddc999dfe6b5d763f41e733 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 17:38:34 +0900 Subject: [PATCH 110/363] Remove remaining `Room!=null` checks in `TestMultiplayerClient` where possible --- .../Visual/Multiplayer/TestMultiplayerClient.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index f2024a3107..6b7495762a 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -87,8 +87,6 @@ namespace osu.Game.Tests.Visual.Multiplayer switch (Room?.MatchState) { case TeamVersusRoomState teamVersus: - Debug.Assert(Room != null); - // simulate the server's automatic assignment of users to teams on join. // the "best" team is the one with the least users on it. int bestTeam = teamVersus.Teams @@ -103,6 +101,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void RemoveUser(APIUser user) { Debug.Assert(Room != null); + ((IMultiplayerClient)this).UserLeft(new MultiplayerRoomUser(user.Id)); Schedule(() => @@ -114,25 +113,23 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeRoomState(MultiplayerRoomState newState) { - Debug.Assert(Room != null); ((IMultiplayerClient)this).RoomStateChanged(newState); } public void ChangeUserState(int userId, MultiplayerUserState newState) { - Debug.Assert(Room != null); - ((IMultiplayerClient)this).UserStateChanged(userId, newState); updateRoomStateIfRequired(); } private void updateRoomStateIfRequired() { - Debug.Assert(Room != null); Debug.Assert(APIRoom != null); Schedule(() => { + Debug.Assert(Room != null); + switch (Room.State) { case MultiplayerRoomState.Open: @@ -179,8 +176,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserBeatmapAvailability(int userId, BeatmapAvailability newBeatmapAvailability) { - Debug.Assert(Room != null); - ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(userId, newBeatmapAvailability); } @@ -290,7 +285,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserMods(int userId, IEnumerable newMods) { - Debug.Assert(Room != null); ((IMultiplayerClient)this).UserModsChanged(userId, newMods.ToList()); } @@ -338,7 +332,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task AbortGameplay() { - Debug.Assert(Room != null); Debug.Assert(LocalUser != null); ChangeUserState(LocalUser.UserID, MultiplayerUserState.Idle); From 4a316fad2fedf334db9761764a46d59fb9e18e3a Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 23 Jun 2022 17:18:37 +0900 Subject: [PATCH 111/363] Add audio feedback for rearranging list items --- .../OsuRearrangeableListContainer.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs index 84d0d1eb32..a314992287 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs @@ -3,9 +3,14 @@ #nullable disable +using System.Collections.Specialized; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; namespace osu.Game.Graphics.Containers { @@ -18,11 +23,44 @@ namespace osu.Game.Graphics.Containers protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + private Sample sampleSwap; + private double sampleLastPlaybackTime; + protected sealed override RearrangeableListItem CreateDrawable(TModel item) => CreateOsuDrawable(item).With(d => { d.DragActive.BindTo(DragActive); }); protected abstract OsuRearrangeableListItem CreateOsuDrawable(TModel item); + + protected OsuRearrangeableListContainer() + { + Items.CollectionChanged += (_, args) => + { + if (args.Action == NotifyCollectionChangedAction.Move) + playSwapSample(); + }; + } + + private void playSwapSample() + { + if (Time.Current - sampleLastPlaybackTime <= 35) + return; + + var channel = sampleSwap?.GetChannel(); + if (channel == null) + return; + + channel.Frequency.Value = 0.96 + RNG.NextDouble(0.08); + channel.Play(); + sampleLastPlaybackTime = Time.Current; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleSwap = audio.Samples.Get(@"UI/item-swap"); + sampleLastPlaybackTime = Time.Current; + } } } From d6b073ebad526db327ad0fcb43d4c9f9dbaf2fbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 16:39:33 +0900 Subject: [PATCH 112/363] Move `DifficultyRetrieve` to own class and split both pathways into separate constructors --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 63 ++++---------- .../Beatmaps/Drawables/DifficultyRetriever.cs | 85 +++++++++++++++++++ 2 files changed, 100 insertions(+), 48 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index a1b0f04aae..a5b13d10e6 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -63,7 +62,8 @@ namespace osu.Game.Beatmaps.Drawables /// The mods to show the difficulty with. /// Whether to display a tooltip when hovered. /// Whether to perform difficulty lookup (including calculation if necessary). - public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true, + bool performBackgroundDifficultyLookup = true) : this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup) { this.ruleset = ruleset ?? beatmapInfo.Ruleset; @@ -125,13 +125,21 @@ namespace osu.Game.Beatmaps.Drawables }; if (performBackgroundDifficultyLookup) - iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0)); + iconContainer.Add(new DelayedLoadUnloadWrapper(createDifficultyRetriever, 0)); else difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarRating, 0); difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars)); } + private Drawable createDifficultyRetriever() + { + if (ruleset != null && mods != null) + return new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }; + + return new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyBindable } }; + } + private Drawable getRulesetIcon() { int? onlineID = (ruleset ?? beatmapInfo.Ruleset).OnlineID; @@ -142,51 +150,10 @@ namespace osu.Game.Beatmaps.Drawables return new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }; } - ITooltip IHasCustomTooltip.GetCustomTooltip() => new DifficultyIconTooltip(); + ITooltip IHasCustomTooltip. + GetCustomTooltip() => new DifficultyIconTooltip(); - DifficultyIconTooltipContent IHasCustomTooltip.TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null; - - private class DifficultyRetriever : Component - { - public readonly Bindable StarDifficulty = new Bindable(); - - private readonly IBeatmapInfo beatmapInfo; - private readonly IRulesetInfo ruleset; - private readonly IReadOnlyList mods; - - private CancellationTokenSource difficultyCancellation; - - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } - - public DifficultyRetriever(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, IReadOnlyList mods) - { - this.beatmapInfo = beatmapInfo; - this.ruleset = ruleset; - this.mods = mods; - } - - private IBindable localStarDifficulty; - - [BackgroundDependencyLoader] - private void load() - { - difficultyCancellation = new CancellationTokenSource(); - localStarDifficulty = ruleset != null - ? difficultyCache.GetBindableDifficulty(beatmapInfo, ruleset, mods, difficultyCancellation.Token) - : difficultyCache.GetBindableDifficulty(beatmapInfo, difficultyCancellation.Token); - localStarDifficulty.BindValueChanged(d => - { - if (d.NewValue is StarDifficulty diff) - StarDifficulty.Value = diff; - }); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - difficultyCancellation?.Cancel(); - } - } + DifficultyIconTooltipContent IHasCustomTooltip. + TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null; } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs b/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs new file mode 100644 index 0000000000..a993f0657b --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Collections.Generic; +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Beatmaps.Drawables +{ + /// + /// A component solely responsible for calculating difficulty in the background. + /// Intended for use with to only run processing when usage is on-screen. + /// + public class DifficultyRetriever : Component + { + /// + /// The bindable star difficulty. + /// + public IBindable StarDifficulty => starDifficulty; + + private readonly Bindable starDifficulty = new Bindable(); + + private readonly IBeatmapInfo beatmapInfo; + + private readonly IRulesetInfo? ruleset; + private readonly IReadOnlyList? mods; + + private readonly CancellationTokenSource difficultyCancellation = new CancellationTokenSource(); + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + + /// + /// Construct a difficulty retriever that tracks the current ruleset / mod selection. + /// + /// The beatmap to use for calculation. + public DifficultyRetriever(IBeatmapInfo beatmapInfo) + { + this.beatmapInfo = beatmapInfo; + } + + /// + /// Construct a difficulty retriever that is calculated only once for the specified ruleset / mod combination. + /// This will not track global ruleset and mod changes. + /// + /// The beatmap to use for calculation. + /// The ruleset to use for calculation. + /// The mods to use for calculation. + public DifficultyRetriever(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, IReadOnlyList mods) + { + this.beatmapInfo = beatmapInfo; + this.ruleset = ruleset; + this.mods = mods; + } + + private IBindable localStarDifficulty = null!; + + [BackgroundDependencyLoader] + private void load() + { + localStarDifficulty = ruleset != null + ? difficultyCache.GetBindableDifficulty(beatmapInfo, ruleset, mods, difficultyCancellation.Token) + : difficultyCache.GetBindableDifficulty(beatmapInfo, difficultyCancellation.Token); + + localStarDifficulty.BindValueChanged(d => + { + if (d.NewValue is StarDifficulty diff) + starDifficulty.Value = diff; + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + difficultyCancellation.Cancel(); + } + } +} From 3a83e5684c64226005ddddc349af36f1defba66a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 16:55:22 +0900 Subject: [PATCH 113/363] Tidy up `DifficultyIcon` --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index a5b13d10e6..48ee1e1fc8 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -26,8 +26,6 @@ namespace osu.Game.Beatmaps.Drawables { public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip { - private readonly Container iconContainer; - /// /// Size of this difficulty icon. /// @@ -46,14 +44,19 @@ namespace osu.Game.Beatmaps.Drawables [CanBeNull] private readonly IReadOnlyList mods; + [Resolved] + private IRulesetStore rulesets { get; set; } + private readonly bool shouldShowTooltip; private readonly bool performBackgroundDifficultyLookup; - private readonly Bindable difficultyBindable = new Bindable(); - private Drawable background; + private readonly Container iconContainer; + + private readonly Bindable difficultyBindable = new Bindable(); + /// /// Creates a new with a given and combination. /// @@ -87,9 +90,6 @@ namespace osu.Game.Beatmaps.Drawables InternalChild = iconContainer = new Container { Size = new Vector2(20f) }; } - [Resolved] - private IRulesetStore rulesets { get; set; } - [BackgroundDependencyLoader] private void load(OsuColour colours) { From 4a2ca4394b7ccd79fe32232ce3a84fa93a3f5234 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 18:37:36 +0900 Subject: [PATCH 114/363] Remove unused `ModeTypeInfo` class --- .../OnlinePlay/Components/ModeTypeInfo.cs | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs diff --git a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs b/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs deleted file mode 100644 index 641776a9e8..0000000000 --- a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Rulesets; -using osuTK; - -namespace osu.Game.Screens.OnlinePlay.Components -{ - public class ModeTypeInfo : OnlinePlayComposite - { - private const float height = 28; - private const float transition_duration = 100; - - [Resolved] - private RulesetStore rulesets { get; set; } - - private Container drawableRuleset; - - public ModeTypeInfo() - { - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - Container gameTypeContainer; - - InternalChild = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5f, 0f), - LayoutDuration = 100, - Children = new[] - { - drawableRuleset = new Container - { - AutoSizeAxes = Axes.Both, - }, - gameTypeContainer = new Container - { - AutoSizeAxes = Axes.Both, - }, - }, - }; - - Type.BindValueChanged(type => gameTypeContainer.Child = new DrawableGameType(type.NewValue) { Size = new Vector2(height) }, true); - - Playlist.CollectionChanged += (_, __) => updateBeatmap(); - - updateBeatmap(); - } - - private void updateBeatmap() - { - var item = Playlist.FirstOrDefault(); - var ruleset = item == null ? null : rulesets.GetRuleset(item.RulesetID)?.CreateInstance(); - - if (item?.Beatmap != null && ruleset != null) - { - var mods = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray(); - - drawableRuleset.FadeIn(transition_duration); - drawableRuleset.Child = new DifficultyIcon(item.Beatmap, ruleset.RulesetInfo, mods) { Size = new Vector2(height) }; - } - else - drawableRuleset.FadeOut(transition_duration); - } - } -} From 7dec530ca5b1a3cdf18c56f76863d9eaf3a2c633 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 18:37:53 +0900 Subject: [PATCH 115/363] Split out simple `DifficultyIcon` with no calculation overhead and update usages --- .../Drawables/CalculatingDifficultyIcon.cs | 80 +++++++++++++++++ osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 89 ++++++------------- .../Drawables/GroupedDifficultyIcon.cs | 2 +- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 3 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- .../Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Carousel/FilterableDifficultyIcon.cs | 4 +- .../Select/Carousel/SetPanelContent.cs | 8 +- 8 files changed, 120 insertions(+), 70 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs diff --git a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs new file mode 100644 index 0000000000..5f914f446c --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs @@ -0,0 +1,80 @@ +#nullable enable +// 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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Beatmaps.Drawables +{ + /// + /// A difficulty icon which automatically calculates difficulty in the background. + /// + public class CalculatingDifficultyIcon : CompositeDrawable + { + /// + /// Size of this difficulty icon. + /// + public new Vector2 Size + { + get => difficultyIcon.Size; + set => difficultyIcon.Size = value; + } + + private readonly IRulesetInfo? ruleset; + + private readonly IReadOnlyList? mods; + + private readonly IBeatmapInfo beatmapInfo; + + private readonly DifficultyIcon difficultyIcon; + + /// + /// Creates a new with a given and combination. + /// + /// The beatmap to show the difficulty of. + /// The ruleset to show the difficulty with. + /// The mods to show the difficulty with. + /// Whether to display a tooltip when hovered. + /// Whether to perform difficulty lookup (including calculation if necessary). + public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo, IRulesetInfo? ruleset, IReadOnlyList? mods, bool shouldShowTooltip = true, + bool performBackgroundDifficultyLookup = true) + : this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup) + { + this.ruleset = ruleset ?? beatmapInfo.Ruleset; + this.mods = mods ?? Array.Empty(); + } + + /// + /// Creates a new that follows the currently-selected ruleset and mods. + /// + /// The beatmap to show the difficulty of. + /// Whether to display a tooltip when hovered. + /// Whether to perform difficulty lookup (including calculation if necessary). + public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + { + this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo)); + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + difficultyIcon = new DifficultyIcon(beatmapInfo, beatmapInfo.Ruleset), + new DelayedLoadUnloadWrapper(createDifficultyRetriever, 0) + }; + } + + private Drawable createDifficultyRetriever() + { + if (ruleset != null && mods != null) + return new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; + + return new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 48ee1e1fc8..d1b15cba76 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -1,11 +1,6 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using System; -using System.Collections.Generic; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -15,16 +10,16 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { - public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip + public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip, IHasCurrentValue { /// /// Size of this difficulty icon. @@ -35,58 +30,46 @@ namespace osu.Game.Beatmaps.Drawables set => iconContainer.Size = value; } - [NotNull] - private readonly IBeatmapInfo beatmapInfo; + private readonly IBeatmapInfo? beatmap; - [CanBeNull] private readonly IRulesetInfo ruleset; - [CanBeNull] - private readonly IReadOnlyList mods; - - [Resolved] - private IRulesetStore rulesets { get; set; } - - private readonly bool shouldShowTooltip; - - private readonly bool performBackgroundDifficultyLookup; - - private Drawable background; + private Drawable background = null!; private readonly Container iconContainer; - private readonly Bindable difficultyBindable = new Bindable(); + private readonly BindableWithCurrent difficulty = new BindableWithCurrent(); + + public virtual Bindable Current + { + get => difficulty.Current; + set => difficulty.Current = value; + } + + [Resolved] + private IRulesetStore rulesets { get; set; } = null!; /// - /// Creates a new with a given and combination. + /// Creates a new with a tooltip. Will use provided beatmap's for initial value. /// - /// The beatmap to show the difficulty of. - /// The ruleset to show the difficulty with. - /// The mods to show the difficulty with. - /// Whether to display a tooltip when hovered. - /// Whether to perform difficulty lookup (including calculation if necessary). - public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true, - bool performBackgroundDifficultyLookup = true) - : this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup) + /// The beatmap to be displayed in the tooltip, and to be used for the initial star rating value. + /// An optional ruleset to be used for the icon display, in place of the beatmap's ruleset. + public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset) + : this(ruleset ?? beatmap.Ruleset) { - this.ruleset = ruleset ?? beatmapInfo.Ruleset; - this.mods = mods ?? Array.Empty(); + this.beatmap = beatmap; + Current.Value = new StarDifficulty(beatmap.StarRating, 0); } /// - /// Creates a new that follows the currently-selected ruleset and mods. + /// Creates a new with no tooltip. /// - /// The beatmap to show the difficulty of. - /// Whether to display a tooltip when hovered. - /// Whether to perform difficulty lookup (including calculation if necessary). - public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + /// The ruleset to be used for the icon display. + public DifficultyIcon(IRulesetInfo ruleset) { - this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo)); - this.shouldShowTooltip = shouldShowTooltip; - this.performBackgroundDifficultyLookup = performBackgroundDifficultyLookup; + this.ruleset = ruleset; AutoSizeAxes = Axes.Both; - InternalChild = iconContainer = new Container { Size = new Vector2(20f) }; } @@ -111,7 +94,6 @@ namespace osu.Game.Beatmaps.Drawables Child = background = new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.ForStarDifficulty(beatmapInfo.StarRating) // Default value that will be re-populated once difficulty calculation completes }, }, new ConstrainedIconContainer @@ -124,25 +106,12 @@ namespace osu.Game.Beatmaps.Drawables }, }; - if (performBackgroundDifficultyLookup) - iconContainer.Add(new DelayedLoadUnloadWrapper(createDifficultyRetriever, 0)); - else - difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarRating, 0); - - difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars)); - } - - private Drawable createDifficultyRetriever() - { - if (ruleset != null && mods != null) - return new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }; - - return new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyBindable } }; + Current.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars), true); } private Drawable getRulesetIcon() { - int? onlineID = (ruleset ?? beatmapInfo.Ruleset).OnlineID; + int? onlineID = ruleset.OnlineID; if (onlineID >= 0 && rulesets.GetRuleset(onlineID.Value)?.CreateInstance() is Ruleset rulesetInstance) return rulesetInstance.CreateIcon(); @@ -154,6 +123,6 @@ namespace osu.Game.Beatmaps.Drawables GetCustomTooltip() => new DifficultyIconTooltip(); DifficultyIconTooltipContent IHasCustomTooltip. - TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null; + TooltipContent => (beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current) : null)!; } } diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs index 15ca4c60d4..a713592246 100644 --- a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs @@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps.Drawables /// /// Used in cases when there are too many difficulty icons to show. /// - public class GroupedDifficultyIcon : DifficultyIcon + public class GroupedDifficultyIcon : CalculatingDifficultyIcon { public GroupedDifficultyIcon(IEnumerable beatmaps, IRulesetInfo ruleset, Color4 counterColour) : base(beatmaps.OrderBy(b => b.StarRating).Last(), ruleset, null, false) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 8d89bb3cc7..2efdb71204 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -274,8 +274,9 @@ namespace osu.Game.Overlays.BeatmapSet Alpha = 0.5f } }, - icon = new DifficultyIcon(beatmapInfo, shouldShowTooltip: false) + icon = new DifficultyIcon(beatmapInfo.Ruleset) { + Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) }, Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(size - tile_icon_padding * 2), diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 53519b8b00..a3a176477e 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -266,7 +266,7 @@ namespace osu.Game.Screens.OnlinePlay } if (beatmap != null) - difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(icon_height) }; + difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset) { Size = new Vector2(icon_height) }; else difficultyIconContainer.Clear(); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 1bea689e7c..79eee63172 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.CentreLeft, Children = new Drawable[] { - new DifficultyIcon(beatmapInfo, shouldShowTooltip: false) + new CalculatingDifficultyIcon(beatmapInfo, shouldShowTooltip: false) { Scale = new Vector2(1.8f), }, diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs index b0841caa47..3f7d5c13ad 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -10,7 +10,7 @@ using osu.Game.Beatmaps.Drawables; namespace osu.Game.Screens.Select.Carousel { - public class FilterableDifficultyIcon : DifficultyIcon + public class FilterableDifficultyIcon : CalculatingDifficultyIcon { private readonly BindableBool filtered = new BindableBool(); @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select.Carousel public readonly CarouselBeatmap Item; public FilterableDifficultyIcon(CarouselBeatmap item) - : base(item.BeatmapInfo, performBackgroundDifficultyLookup: false) + : base(item.BeatmapInfo) { filtered.BindTo(item.Filtered); filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 73a835d67f..c5e5a70d59 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Select.Carousel TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapSet.Status }, - new FillFlowContainer + new FillFlowContainer { AutoSizeAxes = Axes.Both, Spacing = new Vector2(3), @@ -87,13 +87,13 @@ namespace osu.Game.Screens.Select.Carousel private const int maximum_difficulty_icons = 18; - private IEnumerable getDifficultyIcons() + private IEnumerable getDifficultyIcons() { var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) - .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) + ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) + .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } } From 01da6f20b380923a196885e4a789fd29d21e668e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 18:53:21 +0900 Subject: [PATCH 116/363] Tidy up all remaining usages --- .../SongSelect/TestScenePlaySongSelect.cs | 4 +- .../Drawables/CalculatingDifficultyIcon.cs | 40 ++++--------------- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 9 ++++- .../Beatmaps/Drawables/DifficultyRetriever.cs | 2 - .../Drawables/GroupedDifficultyIcon.cs | 39 ------------------ .../Carousel/DrawableCarouselBeatmap.cs | 3 +- .../Carousel/FilterableDifficultyIcon.cs | 2 +- ...icultyIcon.cs => GroupedDifficultyIcon.cs} | 25 ++++++++++-- .../Select/Carousel/SetPanelContent.cs | 8 ++-- 9 files changed, 46 insertions(+), 86 deletions(-) delete mode 100644 osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs rename osu.Game/Screens/Select/Carousel/{FilterableGroupedDifficultyIcon.cs => GroupedDifficultyIcon.cs} (53%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 9fcd470d17..6d881555da 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -872,10 +872,10 @@ namespace osu.Game.Tests.Visual.SongSelect return set != null; }); - FilterableGroupedDifficultyIcon groupIcon = null; + GroupedDifficultyIcon groupIcon = null; AddUntilStep("Find group icon for different ruleset", () => { - return (groupIcon = set.ChildrenOfType() + return (groupIcon = set.ChildrenOfType() .FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.OnlineID == 3)) != null; }); diff --git a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs index 5f914f446c..f8304226e5 100644 --- a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs @@ -1,13 +1,9 @@ -#nullable enable -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osuTK; namespace osu.Game.Beatmaps.Drawables @@ -26,30 +22,16 @@ namespace osu.Game.Beatmaps.Drawables set => difficultyIcon.Size = value; } - private readonly IRulesetInfo? ruleset; - - private readonly IReadOnlyList? mods; + public bool ShowTooltip + { + get => difficultyIcon.ShowTooltip; + set => difficultyIcon.ShowTooltip = value; + } private readonly IBeatmapInfo beatmapInfo; private readonly DifficultyIcon difficultyIcon; - /// - /// Creates a new with a given and combination. - /// - /// The beatmap to show the difficulty of. - /// The ruleset to show the difficulty with. - /// The mods to show the difficulty with. - /// Whether to display a tooltip when hovered. - /// Whether to perform difficulty lookup (including calculation if necessary). - public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo, IRulesetInfo? ruleset, IReadOnlyList? mods, bool shouldShowTooltip = true, - bool performBackgroundDifficultyLookup = true) - : this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup) - { - this.ruleset = ruleset ?? beatmapInfo.Ruleset; - this.mods = mods ?? Array.Empty(); - } - /// /// Creates a new that follows the currently-selected ruleset and mods. /// @@ -64,17 +46,11 @@ namespace osu.Game.Beatmaps.Drawables InternalChildren = new Drawable[] { - difficultyIcon = new DifficultyIcon(beatmapInfo, beatmapInfo.Ruleset), + difficultyIcon = new DifficultyIcon(beatmapInfo), new DelayedLoadUnloadWrapper(createDifficultyRetriever, 0) }; } - private Drawable createDifficultyRetriever() - { - if (ruleset != null && mods != null) - return new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; - - return new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; - } + private Drawable createDifficultyRetriever() => new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index d1b15cba76..03ff0b29b0 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -30,6 +30,11 @@ namespace osu.Game.Beatmaps.Drawables set => iconContainer.Size = value; } + /// + /// Whether to display a tooltip on hover. Only works if a beatmap was provided at construction time. + /// + public bool ShowTooltip { get; set; } = true; + private readonly IBeatmapInfo? beatmap; private readonly IRulesetInfo ruleset; @@ -54,7 +59,7 @@ namespace osu.Game.Beatmaps.Drawables /// /// The beatmap to be displayed in the tooltip, and to be used for the initial star rating value. /// An optional ruleset to be used for the icon display, in place of the beatmap's ruleset. - public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset) + public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null) : this(ruleset ?? beatmap.Ruleset) { this.beatmap = beatmap; @@ -123,6 +128,6 @@ namespace osu.Game.Beatmaps.Drawables GetCustomTooltip() => new DifficultyIconTooltip(); DifficultyIconTooltipContent IHasCustomTooltip. - TooltipContent => (beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current) : null)!; + TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current) : null)!; } } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs b/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs index a993f0657b..90ef89ee36 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable enable - using System.Collections.Generic; using System.Threading; using osu.Framework.Allocation; diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs deleted file mode 100644 index a713592246..0000000000 --- a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; -using osuTK.Graphics; - -namespace osu.Game.Beatmaps.Drawables -{ - /// - /// A difficulty icon that contains a counter on the right-side of it. - /// - /// - /// Used in cases when there are too many difficulty icons to show. - /// - public class GroupedDifficultyIcon : CalculatingDifficultyIcon - { - public GroupedDifficultyIcon(IEnumerable beatmaps, IRulesetInfo ruleset, Color4 counterColour) - : base(beatmaps.OrderBy(b => b.StarRating).Last(), ruleset, null, false) - { - AddInternal(new OsuSpriteText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Padding = new MarginPadding { Left = Size.X }, - Margin = new MarginPadding { Left = 2, Right = 5 }, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), - Text = beatmaps.Count().ToString(), - Colour = counterColour, - }); - } - } -} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 79eee63172..e5fe03f929 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -113,8 +113,9 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.CentreLeft, Children = new Drawable[] { - new CalculatingDifficultyIcon(beatmapInfo, shouldShowTooltip: false) + new CalculatingDifficultyIcon(beatmapInfo) { + ShowTooltip = false, Scale = new Vector2(1.8f), }, new FillFlowContainer diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs index 3f7d5c13ad..cc904fc1da 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -10,7 +10,7 @@ using osu.Game.Beatmaps.Drawables; namespace osu.Game.Screens.Select.Carousel { - public class FilterableDifficultyIcon : CalculatingDifficultyIcon + public class FilterableDifficultyIcon : DifficultyIcon { private readonly BindableBool filtered = new BindableBool(); diff --git a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/GroupedDifficultyIcon.cs similarity index 53% rename from osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs rename to osu.Game/Screens/Select/Carousel/GroupedDifficultyIcon.cs index f883740fd7..8b4140df56 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/GroupedDifficultyIcon.cs @@ -8,23 +8,42 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { - public class FilterableGroupedDifficultyIcon : GroupedDifficultyIcon + /// + /// A difficulty icon that contains a counter on the right-side of it. + /// + /// + /// Used in cases when there are too many difficulty icons to show. + /// + public class GroupedDifficultyIcon : DifficultyIcon { public readonly List Items; - public FilterableGroupedDifficultyIcon(List items, RulesetInfo ruleset) - : base(items.Select(i => i.BeatmapInfo).ToList(), ruleset, Color4.White) + public GroupedDifficultyIcon(List items, RulesetInfo ruleset) + : base(items.OrderBy(b => b.BeatmapInfo.StarRating).Last().BeatmapInfo, ruleset) { Items = items; foreach (var item in items) item.Filtered.BindValueChanged(_ => Scheduler.AddOnce(updateFilteredDisplay)); + AddInternal(new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Padding = new MarginPadding { Left = Size.X }, + Margin = new MarginPadding { Left = 2, Right = 5 }, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), + Text = items.Count.ToString(), + Colour = Color4.White, + }); + updateFilteredDisplay(); } diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index c5e5a70d59..cc3d722852 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Select.Carousel TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapSet.Status }, - new FillFlowContainer + new FillFlowContainer { AutoSizeAxes = Axes.Both, Spacing = new Vector2(3), @@ -87,13 +87,13 @@ namespace osu.Game.Screens.Select.Carousel private const int maximum_difficulty_icons = 18; - private IEnumerable getDifficultyIcons() + private IEnumerable getDifficultyIcons() { var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) - .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) + ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) + .Select(group => new GroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } } From 32652ace75b2a3954e359f4af915385b77262b8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 19:51:44 +0900 Subject: [PATCH 117/363] 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 caea787e22..f2790d2520 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 652dc2740f..0802a98858 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 0c3c38f9f0..a272df20d4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From b068df21497cba26067b98ed8d7356634421fb5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 19:44:38 +0900 Subject: [PATCH 118/363] Enable NRT on `BeatmapDiffiultyCache` --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 45 +++++++++------------ 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 69488277f1..b6314f5841 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -1,15 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -50,19 +47,19 @@ namespace osu.Game.Beatmaps /// private readonly object bindableUpdateLock = new object(); - private CancellationTokenSource trackedUpdateCancellationSource; + private CancellationTokenSource trackedUpdateCancellationSource = new CancellationTokenSource(); [Resolved] - private BeatmapManager beatmapManager { get; set; } + private BeatmapManager beatmapManager { get; set; } = null!; [Resolved] - private Bindable currentRuleset { get; set; } + private Bindable currentRuleset { get; set; } = null!; [Resolved] - private Bindable> currentMods { get; set; } + private Bindable> currentMods { get; set; } = null!; - private ModSettingChangeTracker modSettingChangeTracker; - private ScheduledDelegate debouncedModSettingsChange; + private ModSettingChangeTracker? modSettingChangeTracker; + private ScheduledDelegate? debouncedModSettingsChange; protected override void LoadComplete() { @@ -91,7 +88,7 @@ namespace osu.Game.Beatmaps /// The to get the difficulty of. /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated). - public IBindable GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) + public IBindable GetBindableDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) { var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); @@ -112,7 +109,7 @@ namespace osu.Game.Beatmaps /// The s to get the difficulty with. If null, no mods will be assumed. /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state. - public IBindable GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, + public IBindable GetBindableDifficulty(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo, IEnumerable? mods, CancellationToken cancellationToken = default) => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); @@ -128,8 +125,8 @@ namespace osu.Game.Beatmaps /// A return value indicates that the difficulty process failed or was interrupted early, /// and as such there is no usable star difficulty value to be returned. /// - public virtual Task GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null, - [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default) + public virtual Task GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo = null, + IEnumerable? mods = null, CancellationToken cancellationToken = default) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. rulesetInfo ??= beatmapInfo.Ruleset; @@ -204,7 +201,6 @@ namespace osu.Game.Beatmaps lock (bindableUpdateLock) { cancelTrackedBindableUpdate(); - trackedUpdateCancellationSource = new CancellationTokenSource(); foreach (var b in trackedBindables) { @@ -223,16 +219,13 @@ namespace osu.Game.Beatmaps { lock (bindableUpdateLock) { - trackedUpdateCancellationSource?.Cancel(); - trackedUpdateCancellationSource = null; + trackedUpdateCancellationSource.Cancel(); + trackedUpdateCancellationSource = new CancellationTokenSource(); - if (linkedCancellationSources != null) - { - foreach (var c in linkedCancellationSources) - c.Dispose(); + foreach (var c in linkedCancellationSources) + c.Dispose(); - linkedCancellationSources.Clear(); - } + linkedCancellationSources.Clear(); } } @@ -244,7 +237,7 @@ namespace osu.Game.Beatmaps /// The initial s to get the difficulty with. /// An optional which stops updating the star difficulty for the given . /// The . - private BindableStarDifficulty createBindable([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable initialMods, + private BindableStarDifficulty createBindable(IBeatmapInfo beatmapInfo, IRulesetInfo? initialRulesetInfo, IEnumerable? initialMods, CancellationToken cancellationToken) { var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); @@ -259,7 +252,7 @@ namespace osu.Game.Beatmaps /// The to update with. /// The s to update with. /// A token that may be used to cancel this update. - private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default) + private void updateBindable(BindableStarDifficulty bindable, IRulesetInfo? rulesetInfo, IEnumerable? mods, CancellationToken cancellationToken = default) { // GetDifficultyAsync will fall back to existing data from IBeatmapInfo if not locally available // (contrary to GetAsync) @@ -329,7 +322,7 @@ namespace osu.Game.Beatmaps modSettingChangeTracker?.Dispose(); cancelTrackedBindableUpdate(); - updateScheduler?.Dispose(); + updateScheduler.Dispose(); } public readonly struct DifficultyCacheLookup : IEquatable @@ -339,7 +332,7 @@ namespace osu.Game.Beatmaps public readonly Mod[] OrderedMods; - public DifficultyCacheLookup([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo ruleset, IEnumerable mods) + public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo? ruleset, IEnumerable? mods) { BeatmapInfo = beatmapInfo; // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. From ef258122d23b4c2fe1a24756f38e266a7597b2e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Jun 2022 19:46:59 +0900 Subject: [PATCH 119/363] Move `GetDifficultyRating` helper method to `StarDifficulty` --- .../TestSceneBeatmapDifficultyCache.cs | 2 +- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 29 ----------------- osu.Game/Beatmaps/EFBeatmapInfo.cs | 2 +- osu.Game/Beatmaps/StarDifficulty.cs | 31 ++++++++++++++++++- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 2 +- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index f87e5711a6..6eb103316f 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Beatmaps [TestCase(8.3, DifficultyRating.ExpertPlus)] public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket) { - var actualBracket = BeatmapDifficultyCache.GetDifficultyRating(starRating); + var actualBracket = StarDifficulty.GetDifficultyRating(starRating); Assert.AreEqual(expectedBracket, actualBracket); } diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index b6314f5841..5e468e975a 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -13,7 +13,6 @@ using osu.Framework.Extensions; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Threading; -using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Rulesets; @@ -165,34 +164,6 @@ namespace osu.Game.Beatmaps updateScheduler); } - /// - /// Retrieves the that describes a star rating. - /// - /// - /// For more information, see: https://osu.ppy.sh/help/wiki/Difficulties - /// - /// The star rating. - /// The that best describes . - public static DifficultyRating GetDifficultyRating(double starRating) - { - if (Precision.AlmostBigger(starRating, 6.5, 0.005)) - return DifficultyRating.ExpertPlus; - - if (Precision.AlmostBigger(starRating, 5.3, 0.005)) - return DifficultyRating.Expert; - - if (Precision.AlmostBigger(starRating, 4.0, 0.005)) - return DifficultyRating.Insane; - - if (Precision.AlmostBigger(starRating, 2.7, 0.005)) - return DifficultyRating.Hard; - - if (Precision.AlmostBigger(starRating, 2.0, 0.005)) - return DifficultyRating.Normal; - - return DifficultyRating.Easy; - } - /// /// Updates all tracked using the current ruleset and mods. /// diff --git a/osu.Game/Beatmaps/EFBeatmapInfo.cs b/osu.Game/Beatmaps/EFBeatmapInfo.cs index 34311448eb..20abdc686a 100644 --- a/osu.Game/Beatmaps/EFBeatmapInfo.cs +++ b/osu.Game/Beatmaps/EFBeatmapInfo.cs @@ -128,7 +128,7 @@ namespace osu.Game.Beatmaps public List Scores { get; set; } [JsonIgnore] - public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarRating); + public DifficultyRating DifficultyRating => StarDifficulty.GetDifficultyRating(StarRating); public override string ToString() => this.GetDisplayTitle(); diff --git a/osu.Game/Beatmaps/StarDifficulty.cs b/osu.Game/Beatmaps/StarDifficulty.cs index 91bc3aacf6..e042f1c698 100644 --- a/osu.Game/Beatmaps/StarDifficulty.cs +++ b/osu.Game/Beatmaps/StarDifficulty.cs @@ -4,6 +4,7 @@ #nullable disable using JetBrains.Annotations; +using osu.Framework.Utils; using osu.Game.Rulesets.Difficulty; namespace osu.Game.Beatmaps @@ -50,6 +51,34 @@ namespace osu.Game.Beatmaps Attributes = null; } - public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(Stars); + public DifficultyRating DifficultyRating => GetDifficultyRating(Stars); + + /// + /// Retrieves the that describes a star rating. + /// + /// + /// For more information, see: https://osu.ppy.sh/help/wiki/Difficulties + /// + /// The star rating. + /// The that best describes . + public static DifficultyRating GetDifficultyRating(double starRating) + { + if (Precision.AlmostBigger(starRating, 6.5, 0.005)) + return DifficultyRating.ExpertPlus; + + if (Precision.AlmostBigger(starRating, 5.3, 0.005)) + return DifficultyRating.Expert; + + if (Precision.AlmostBigger(starRating, 4.0, 0.005)) + return DifficultyRating.Insane; + + if (Precision.AlmostBigger(starRating, 2.7, 0.005)) + return DifficultyRating.Hard; + + if (Precision.AlmostBigger(starRating, 2.0, 0.005)) + return DifficultyRating.Normal; + + return DifficultyRating.Easy; + } } } diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index d5b32760c9..3030018138 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Edit.Verify [BackgroundDependencyLoader] private void load() { - InterpretedDifficulty.Default = BeatmapDifficultyCache.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating); + InterpretedDifficulty.Default = StarDifficulty.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating); InterpretedDifficulty.SetDefault(); Child = new Container From 9c46592e0e45279e4a92d4f9b415b61b17510787 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 23 Jun 2022 20:04:54 +0900 Subject: [PATCH 120/363] Move collection change event binding to LoadComplete --- osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs index a314992287..4cf2c3778a 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs @@ -33,8 +33,10 @@ namespace osu.Game.Graphics.Containers protected abstract OsuRearrangeableListItem CreateOsuDrawable(TModel item); - protected OsuRearrangeableListContainer() + protected override void LoadComplete() { + base.LoadComplete(); + Items.CollectionChanged += (_, args) => { if (args.Action == NotifyCollectionChangedAction.Move) From aaf619b35c8d6b8642c6d89d83cdb74a4758df00 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 23 Jun 2022 20:13:01 +0900 Subject: [PATCH 121/363] Don't play sample if there is no item being actively dragged --- osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs index 4cf2c3778a..b604ae73eb 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs @@ -46,6 +46,9 @@ namespace osu.Game.Graphics.Containers private void playSwapSample() { + if (!DragActive.Value) + return; + if (Time.Current - sampleLastPlaybackTime <= 35) return; From 2def6d809bf4c48108a95324101c5a1c107b3bfa Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 23 Jun 2022 11:02:27 -0400 Subject: [PATCH 122/363] refactor to use enum dropdown --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 12 +++++------- .../OnlinePlay/Lounge/Components/FilterCriteria.cs | 2 +- .../Lounge/Components/RoomPublicityFilter.cs | 13 +++++++++++++ .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- .../Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 13 ++++++------- 5 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 363a2b4a4d..4ebd05d46a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestLockedFiltering() + public void TestPublicityFiltering() { AddStep("add rooms", () => { @@ -166,15 +166,13 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomManager.AddRooms(1, withPassword: false); }); - AddUntilStep("both rooms shown", () => container.Rooms.Count(r => r.IsPresent) == 2); + AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Publicity = RoomPublicityFilter.Public }); - AddStep("filter locked rooms", () => container.Filter.Value = new FilterCriteria { Locked = false }); + AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); - AddUntilStep("locked room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); + AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { Publicity = RoomPublicityFilter.Private }); - AddStep("unfilter locked rooms", () => container.Filter.SetDefault()); - - AddUntilStep("both rooms shown", () => container.Rooms.Count(r => r.IsPresent) == 2); + AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword.Value)); } [Test] diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index 45c1b9b394..3bde1f07b8 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -13,6 +13,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public RoomStatusFilter Status; public string Category; public RulesetInfo Ruleset; - public bool Locked = true; + public RoomPublicityFilter Publicity; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs new file mode 100644 index 0000000000..ffa527cb9c --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +namespace osu.Game.Screens.OnlinePlay.Lounge.Components +{ + public enum RoomPublicityFilter + { + Public, + Private + } +} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 908505e386..e5d7170f16 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } - matchingFilter &= criteria.Locked || !r.Room.HasPassword.Value; + matchingFilter &= (criteria.Publicity == RoomPublicityFilter.Public && !r.Room.HasPassword.Value) || (criteria.Publicity == RoomPublicityFilter.Private && r.Room.HasPassword.Value); r.MatchingFilter = matchingFilter; } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index a726ff632f..7ec2c7d8e7 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private RoomsContainer roomsContainer; private SearchTextBox searchTextBox; private Dropdown statusDropdown; - private Checkbox lockedCheckbox; + private Dropdown publicityDropdown; [BackgroundDependencyLoader(true)] private void load([CanBeNull] IdleTracker idleTracker) @@ -226,7 +226,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge SearchString = searchTextBox.Current.Value, Ruleset = ruleset.Value, Status = statusDropdown.Current.Value, - Locked = lockedCheckbox.Current.Value, + Publicity = publicityDropdown.Current.Value, }; #endregion @@ -241,16 +241,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge statusDropdown.Current.BindValueChanged(_ => UpdateFilter()); - lockedCheckbox = new OsuTabControlCheckbox + publicityDropdown = new SlimEnumDropdown { - Current = new Bindable(true), RelativeSizeAxes = Axes.None, - Text = @"Show Locked", + Width = 160, }; - lockedCheckbox.Current.BindValueChanged(_ => UpdateFilter()); + publicityDropdown.Current.BindValueChanged(_ => UpdateFilter()); - return new Drawable[] { lockedCheckbox, statusDropdown, }; + return new Drawable[] { publicityDropdown, statusDropdown, }; } public override void OnEntering(ScreenTransitionEvent e) From 9a15adbfffb4b4db274b71db5cc00b20694ccf32 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 23 Jun 2022 11:24:28 -0400 Subject: [PATCH 123/363] add an all option to publicity filter --- .../Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs | 4 ++++ .../OnlinePlay/Lounge/Components/RoomPublicityFilter.cs | 1 + .../Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 4ebd05d46a..e16e71ca70 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -166,6 +166,10 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomManager.AddRooms(1, withPassword: false); }); + AddStep("apply default filter", () => container.Filter.SetDefault()); + + AddUntilStep("both rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2); + AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Publicity = RoomPublicityFilter.Public }); AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs index ffa527cb9c..53f85ba7c4 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs @@ -7,6 +7,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public enum RoomPublicityFilter { + All, Public, Private } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index e5d7170f16..0e8918fa18 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } - matchingFilter &= (criteria.Publicity == RoomPublicityFilter.Public && !r.Room.HasPassword.Value) || (criteria.Publicity == RoomPublicityFilter.Private && r.Room.HasPassword.Value); + matchingFilter &= criteria.Publicity == RoomPublicityFilter.All || (criteria.Publicity == RoomPublicityFilter.Public && !r.Room.HasPassword.Value) || (criteria.Publicity == RoomPublicityFilter.Private && r.Room.HasPassword.Value); r.MatchingFilter = matchingFilter; } From d67c482c48b9cf3175672edccc84a91ec5d17c84 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 23 Jun 2022 11:36:08 -0400 Subject: [PATCH 124/363] move publicity filter to multi exclusively --- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 18 ++++------------- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 7ec2c7d8e7..f7f3c27ede 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -85,7 +85,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private RoomsContainer roomsContainer; private SearchTextBox searchTextBox; private Dropdown statusDropdown; - private Dropdown publicityDropdown; [BackgroundDependencyLoader(true)] private void load([CanBeNull] IdleTracker idleTracker) @@ -225,12 +224,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { SearchString = searchTextBox.Current.Value, Ruleset = ruleset.Value, - Status = statusDropdown.Current.Value, - Publicity = publicityDropdown.Current.Value, + Status = statusDropdown.Current.Value }; - #endregion - protected virtual IEnumerable CreateFilterControls() { statusDropdown = new SlimEnumDropdown @@ -241,17 +237,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge statusDropdown.Current.BindValueChanged(_ => UpdateFilter()); - publicityDropdown = new SlimEnumDropdown - { - RelativeSizeAxes = Axes.None, - Width = 160, - }; - - publicityDropdown.Current.BindValueChanged(_ => UpdateFilter()); - - return new Drawable[] { publicityDropdown, statusDropdown, }; + yield return statusDropdown; } + #endregion + public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index ae65e1d969..2c0f0f43fe 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -3,11 +3,15 @@ #nullable disable +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; @@ -27,6 +31,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } + private Dropdown publicityDropdown; + public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); @@ -40,10 +46,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } + protected override IEnumerable CreateFilterControls() + { + publicityDropdown = new SlimEnumDropdown + { + RelativeSizeAxes = Axes.None, + Width = 160, + }; + + publicityDropdown.Current.BindValueChanged(_ => UpdateFilter()); + + return base.CreateFilterControls().Prepend(publicityDropdown); + } + protected override FilterCriteria CreateFilterCriteria() { var criteria = base.CreateFilterCriteria(); criteria.Category = @"realtime"; + criteria.Publicity = publicityDropdown.Current.Value; return criteria; } From 454eff43c542fdad3a37a37ea66bad6c594aa5ba Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 23 Jun 2022 11:40:25 -0400 Subject: [PATCH 125/363] publicity -> accesstype --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 4 ++-- .../OnlinePlay/Lounge/Components/FilterCriteria.cs | 2 +- .../{RoomPublicityFilter.cs => RoomAccessType.cs} | 2 +- .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 10 +++++----- 5 files changed, 10 insertions(+), 10 deletions(-) rename osu.Game/Screens/OnlinePlay/Lounge/Components/{RoomPublicityFilter.cs => RoomAccessType.cs} (88%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index e16e71ca70..89be9cc188 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -170,11 +170,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("both rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2); - AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Publicity = RoomPublicityFilter.Public }); + AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { AccessType = RoomAccessType.Public }); AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); - AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { Publicity = RoomPublicityFilter.Private }); + AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { AccessType = RoomAccessType.Private }); AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword.Value)); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index 3bde1f07b8..864634209b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -13,6 +13,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public RoomStatusFilter Status; public string Category; public RulesetInfo Ruleset; - public RoomPublicityFilter Publicity; + public RoomAccessType AccessType; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs similarity index 88% rename from osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs rename to osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs index 53f85ba7c4..bf8da65881 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPublicityFilter.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs @@ -5,7 +5,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public enum RoomPublicityFilter + public enum RoomAccessType { All, Public, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 0e8918fa18..55fe8c129a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } - matchingFilter &= criteria.Publicity == RoomPublicityFilter.All || (criteria.Publicity == RoomPublicityFilter.Public && !r.Room.HasPassword.Value) || (criteria.Publicity == RoomPublicityFilter.Private && r.Room.HasPassword.Value); + matchingFilter &= criteria.AccessType == RoomAccessType.All || (criteria.AccessType == RoomAccessType.Public && !r.Room.HasPassword.Value) || (criteria.AccessType == RoomAccessType.Private && r.Room.HasPassword.Value); r.MatchingFilter = matchingFilter; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 2c0f0f43fe..63a482ce46 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } - private Dropdown publicityDropdown; + private Dropdown roomAccessTypeDropdown; public override void OnResuming(ScreenTransitionEvent e) { @@ -48,22 +48,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override IEnumerable CreateFilterControls() { - publicityDropdown = new SlimEnumDropdown + roomAccessTypeDropdown = new SlimEnumDropdown { RelativeSizeAxes = Axes.None, Width = 160, }; - publicityDropdown.Current.BindValueChanged(_ => UpdateFilter()); + roomAccessTypeDropdown.Current.BindValueChanged(_ => UpdateFilter()); - return base.CreateFilterControls().Prepend(publicityDropdown); + return base.CreateFilterControls().Prepend(roomAccessTypeDropdown); } protected override FilterCriteria CreateFilterCriteria() { var criteria = base.CreateFilterCriteria(); criteria.Category = @"realtime"; - criteria.Publicity = publicityDropdown.Current.Value; + criteria.AccessType = roomAccessTypeDropdown.Current.Value; return criteria; } From bea70988bc09fe4cbed5723715175248c4eedf3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Jun 2022 17:57:22 +0200 Subject: [PATCH 126/363] Remove unnecessary `#nullable disable` --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs index bf8da65881..1141ed2937 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public enum RoomAccessType From 04d69010b5666e1eb57ac02824c0fc2e2e257001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Jun 2022 18:03:04 +0200 Subject: [PATCH 127/363] Split overlong conditional to local function --- .../Lounge/Components/RoomsContainer.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 55fe8c129a..24dd27c27b 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,11 +87,29 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } - matchingFilter &= criteria.AccessType == RoomAccessType.All || (criteria.AccessType == RoomAccessType.Public && !r.Room.HasPassword.Value) || (criteria.AccessType == RoomAccessType.Private && r.Room.HasPassword.Value); + matchingFilter &= matchesAccessType(r, criteria.AccessType); r.MatchingFilter = matchingFilter; } }); + + static bool matchesAccessType(DrawableLoungeRoom room, RoomAccessType accessType) + { + switch (accessType) + { + case RoomAccessType.All: + return true; + + case RoomAccessType.Public: + return !room.Room.HasPassword.Value; + + case RoomAccessType.Private: + return room.Room.HasPassword.Value; + + default: + throw new ArgumentOutOfRangeException(nameof(accessType), accessType, $"Unsupported {nameof(RoomAccessType)} in filter"); + } + } } private void roomsChanged(object sender, NotifyCollectionChangedEventArgs args) From 489ed5d34d2fd8ead9ed49a7eda7e9361e1a8b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 23 Jun 2022 18:04:38 +0200 Subject: [PATCH 128/363] Rename test method --- .../Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 89be9cc188..d56716a0b5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestPublicityFiltering() + public void TestAccessTypeFiltering() { AddStep("add rooms", () => { From eaae600a49b08299a96fdae2ec44b57712184758 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 24 Jun 2022 04:57:44 +0300 Subject: [PATCH 129/363] Fix typo in auto-import ignore rule --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index e732b25951..8fd750a50d 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -793,7 +793,7 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True - True + True True True True From 129c90709252aa6fc4dd11b69b0616fb4915dee8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 13:58:02 +0900 Subject: [PATCH 130/363] Remove unused parameters --- osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs index f8304226e5..2adba3ae99 100644 --- a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs @@ -36,9 +36,7 @@ namespace osu.Game.Beatmaps.Drawables /// Creates a new that follows the currently-selected ruleset and mods. /// /// The beatmap to show the difficulty of. - /// Whether to display a tooltip when hovered. - /// Whether to perform difficulty lookup (including calculation if necessary). - public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo) { this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo)); From d9c1a9d71fd16c10601654af54e652533cbb7b0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 13:59:17 +0900 Subject: [PATCH 131/363] Use new property to specify no tooltip, rather than passing only ruleset --- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 4 ++-- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 03ff0b29b0..679e9c3665 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -55,7 +55,7 @@ namespace osu.Game.Beatmaps.Drawables private IRulesetStore rulesets { get; set; } = null!; /// - /// Creates a new with a tooltip. Will use provided beatmap's for initial value. + /// Creates a new . Will use provided beatmap's for initial value. /// /// The beatmap to be displayed in the tooltip, and to be used for the initial star rating value. /// An optional ruleset to be used for the icon display, in place of the beatmap's ruleset. @@ -67,7 +67,7 @@ namespace osu.Game.Beatmaps.Drawables } /// - /// Creates a new with no tooltip. + /// Creates a new without an associated beatmap. /// /// The ruleset to be used for the icon display. public DifficultyIcon(IRulesetInfo ruleset) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 2efdb71204..1a57a89957 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -274,8 +274,9 @@ namespace osu.Game.Overlays.BeatmapSet Alpha = 0.5f } }, - icon = new DifficultyIcon(beatmapInfo.Ruleset) + icon = new DifficultyIcon(beatmapInfo) { + ShowTooltip = false, Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) }, Anchor = Anchor.Centre, Origin = Anchor.Centre, From 12ea8369ee504caddcaf38488cae09bfedb091b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 14:06:31 +0900 Subject: [PATCH 132/363] Update retriever to be relatively sized --- .../Beatmaps/Drawables/CalculatingDifficultyIcon.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs index 2adba3ae99..df9766c891 100644 --- a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osuTK; @@ -28,8 +27,6 @@ namespace osu.Game.Beatmaps.Drawables set => difficultyIcon.ShowTooltip = value; } - private readonly IBeatmapInfo beatmapInfo; - private readonly DifficultyIcon difficultyIcon; /// @@ -38,17 +35,16 @@ namespace osu.Game.Beatmaps.Drawables /// The beatmap to show the difficulty of. public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo) { - this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo)); - AutoSizeAxes = Axes.Both; InternalChildren = new Drawable[] { difficultyIcon = new DifficultyIcon(beatmapInfo), - new DelayedLoadUnloadWrapper(createDifficultyRetriever, 0) + new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }, 0) + { + RelativeSizeAxes = Axes.Both, + } }; } - - private Drawable createDifficultyRetriever() => new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }; } } From 28837693e5a72ccf692df76a7bc231a473987c4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 14:22:38 +0900 Subject: [PATCH 133/363] Nuke calculating everything The whole component is pointless so I'm just going to nuke for now I guess. Kind of makes the whole refactor effort pointless but oh well? To expand on this, the implementation was actually incorrect as pointed out at https://github.com/ppy/osu/pull/18819#pullrequestreview-1017886035. --- .../Drawables/CalculatingDifficultyIcon.cs | 50 ----------- .../Beatmaps/Drawables/DifficultyRetriever.cs | 83 ------------------- .../Carousel/DrawableCarouselBeatmap.cs | 6 +- 3 files changed, 5 insertions(+), 134 deletions(-) delete mode 100644 osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs delete mode 100644 osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs diff --git a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs deleted file mode 100644 index df9766c891..0000000000 --- a/osu.Game/Beatmaps/Drawables/CalculatingDifficultyIcon.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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.Graphics; -using osu.Framework.Graphics.Containers; -using osuTK; - -namespace osu.Game.Beatmaps.Drawables -{ - /// - /// A difficulty icon which automatically calculates difficulty in the background. - /// - public class CalculatingDifficultyIcon : CompositeDrawable - { - /// - /// Size of this difficulty icon. - /// - public new Vector2 Size - { - get => difficultyIcon.Size; - set => difficultyIcon.Size = value; - } - - public bool ShowTooltip - { - get => difficultyIcon.ShowTooltip; - set => difficultyIcon.ShowTooltip = value; - } - - private readonly DifficultyIcon difficultyIcon; - - /// - /// Creates a new that follows the currently-selected ruleset and mods. - /// - /// The beatmap to show the difficulty of. - public CalculatingDifficultyIcon(IBeatmapInfo beatmapInfo) - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - difficultyIcon = new DifficultyIcon(beatmapInfo), - new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo) { StarDifficulty = { BindTarget = difficultyIcon.Current } }, 0) - { - RelativeSizeAxes = Axes.Both, - } - }; - } - } -} diff --git a/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs b/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs deleted file mode 100644 index 90ef89ee36..0000000000 --- a/osu.Game/Beatmaps/Drawables/DifficultyRetriever.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; - -namespace osu.Game.Beatmaps.Drawables -{ - /// - /// A component solely responsible for calculating difficulty in the background. - /// Intended for use with to only run processing when usage is on-screen. - /// - public class DifficultyRetriever : Component - { - /// - /// The bindable star difficulty. - /// - public IBindable StarDifficulty => starDifficulty; - - private readonly Bindable starDifficulty = new Bindable(); - - private readonly IBeatmapInfo beatmapInfo; - - private readonly IRulesetInfo? ruleset; - private readonly IReadOnlyList? mods; - - private readonly CancellationTokenSource difficultyCancellation = new CancellationTokenSource(); - - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } = null!; - - /// - /// Construct a difficulty retriever that tracks the current ruleset / mod selection. - /// - /// The beatmap to use for calculation. - public DifficultyRetriever(IBeatmapInfo beatmapInfo) - { - this.beatmapInfo = beatmapInfo; - } - - /// - /// Construct a difficulty retriever that is calculated only once for the specified ruleset / mod combination. - /// This will not track global ruleset and mod changes. - /// - /// The beatmap to use for calculation. - /// The ruleset to use for calculation. - /// The mods to use for calculation. - public DifficultyRetriever(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, IReadOnlyList mods) - { - this.beatmapInfo = beatmapInfo; - this.ruleset = ruleset; - this.mods = mods; - } - - private IBindable localStarDifficulty = null!; - - [BackgroundDependencyLoader] - private void load() - { - localStarDifficulty = ruleset != null - ? difficultyCache.GetBindableDifficulty(beatmapInfo, ruleset, mods, difficultyCancellation.Token) - : difficultyCache.GetBindableDifficulty(beatmapInfo, difficultyCancellation.Token); - - localStarDifficulty.BindValueChanged(d => - { - if (d.NewValue is StarDifficulty diff) - starDifficulty.Value = diff; - }); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - difficultyCancellation.Cancel(); - } - } -} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index e5fe03f929..1b3cab20e8 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -53,7 +53,9 @@ namespace osu.Game.Screens.Select.Carousel private Action hideRequested; private Triangles triangles; + private StarCounter starCounter; + private DifficultyIcon difficultyIcon; [Resolved(CanBeNull = true)] private BeatmapSetOverlay beatmapOverlay { get; set; } @@ -113,7 +115,7 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.CentreLeft, Children = new Drawable[] { - new CalculatingDifficultyIcon(beatmapInfo) + difficultyIcon = new DifficultyIcon(beatmapInfo) { ShowTooltip = false, Scale = new Vector2(1.8f), @@ -217,6 +219,8 @@ namespace osu.Game.Screens.Select.Carousel starDifficultyBindable.BindValueChanged(d => { starCounter.Current = (float)(d.NewValue?.Stars ?? 0); + if (d.NewValue != null) + difficultyIcon.Current.Value = d.NewValue.Value; }, true); } From f4173a3bff4a170e78e6215a6a82a556798bee31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 14:48:32 +0900 Subject: [PATCH 134/363] Update c# language version to 10 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index dbc84fb88f..1f59f19eaa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@  - 8.0 + 10.0 true enable From f71f6302fd494ec8d12136f6709f36b705ac70ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 14:48:43 +0900 Subject: [PATCH 135/363] Remove unnecessary `null` casts --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 2 +- osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs | 4 ++-- osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs | 2 +- .../Visual/UserInterface/TestSceneLabelledDrawable.cs | 2 +- .../Visual/UserInterface/TestSceneOsuAnimatedButton.cs | 3 +-- osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs | 3 +-- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 +- osu.Game/Graphics/UserInterface/ShearedToggleButton.cs | 3 +-- osu.Game/Graphics/UserInterface/ToggleMenuItem.cs | 2 +- osu.Game/OsuGameBase.cs | 4 ++-- osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs | 4 ++-- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- osu.Game/Overlays/Mods/ModPanel.cs | 6 +++--- osu.Game/Rulesets/EFRulesetInfo.cs | 2 +- osu.Game/Rulesets/Objects/SliderPath.cs | 2 +- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 2 +- .../Screens/Edit/Components/Menus/DifficultyMenuItem.cs | 2 +- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 2 +- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 4 ++-- osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 4 ++-- osu.Game/Storyboards/CommandTimelineGroup.cs | 2 +- 23 files changed, 30 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index be2e9c7cb5..5706955fc5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Tests => addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame); private Func timeAtRepeat(Func startTime, int repeatIndex) => () => startTime() + 100 + duration_of_span * repeatIndex; - private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)getSliderStart : getSliderEnd; + private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? getSliderStart : getSliderEnd; private List getSliderCurve() => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; private Vector2 getSliderStart() => getSliderCurve().First(); diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index 4a5bb6de46..541ad1e8bb 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -364,12 +364,12 @@ namespace osu.Game.Tests.NonVisual private void confirmCurrentFrame(int? frame) { - Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.CurrentFrame?.Time, "Unexpected current frame"); + Assert.AreEqual(frame is int x ? replay.Frames[x].Time : null, handler.CurrentFrame?.Time, "Unexpected current frame"); } private void confirmNextFrame(int? frame) { - Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.NextFrame?.Time, "Unexpected next frame"); + Assert.AreEqual(frame is int x ? replay.Frames[x].Time : null, handler.NextFrame?.Time, "Unexpected next frame"); } private class TestReplayFrame : ReplayFrame diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs index 77ceef6402..1493c10969 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Skins { AddStep($"Set beatmap skin enabled to {allowBeatmapLookups}", () => config.SetValue(OsuSetting.BeatmapSkins, allowBeatmapLookups)); - ISkin expected() => allowBeatmapLookups ? (ISkin)beatmapSource : userSource; + ISkin expected() => allowBeatmapLookups ? beatmapSource : userSource; AddAssert("Check lookup is from correct source", () => requester.FindProvider(s => s.GetDrawableComponent(new TestSkinComponent()) != null) == expected()); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs index f717bb4dee..7ce0fceff9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Width = 500, AutoSizeAxes = Axes.Y, - Child = component = padded ? (LabelledDrawable)new PaddedLabelledDrawable() : new NonPaddedLabelledDrawable(), + Child = component = padded ? new PaddedLabelledDrawable() : new NonPaddedLabelledDrawable(), }; component.Label = "a sample component"; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs index 28599c740e..bab2121d70 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; @@ -89,7 +88,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddToggleStep("toggle enabled", toggle => { for (int i = 0; i < 6; i++) - button.Action = toggle ? () => { } : (Action)null; + button.Action = toggle ? () => { } : null; }); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs index 0e31a133ac..d4c2bfd422 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -29,7 +28,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddToggleStep("toggle enabled", toggle => { for (int i = 0; i < 6; i++) - button.Action = toggle ? () => { } : (Action)null; + button.Action = toggle ? () => { } : null; }); } diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 5c8579a144..17c51129a7 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -260,7 +260,7 @@ namespace osu.Game.Graphics.Containers if (host.Window == null) return; bool coversWholeScreen = Size == Vector2.One && safeArea.SafeAreaPadding.Value.Total == Vector2.Zero; - host.Window.CursorConfineRect = coversWholeScreen ? (RectangleF?)null : ToScreenSpace(DrawRectangle).AABBFloat; + host.Window.CursorConfineRect = coversWholeScreen ? null : ToScreenSpace(DrawRectangle).AABBFloat; } } } diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs index 50682ddfe0..0bbcb2b976 100644 --- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.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 osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -47,7 +46,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { - Active.BindDisabledChanged(disabled => Action = disabled ? (Action?)null : Active.Toggle, true); + Active.BindDisabledChanged(disabled => Action = disabled ? null : Active.Toggle, true); Active.BindValueChanged(_ => { updateActiveState(); diff --git a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs index 9d1650c970..6787e17113 100644 --- a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs @@ -34,6 +34,6 @@ namespace osu.Game.Graphics.UserInterface { } - public override IconUsage? GetIconForState(bool state) => state ? (IconUsage?)FontAwesome.Solid.Check : null; + public override IconUsage? GetIconForState(bool state) => state ? FontAwesome.Solid.Check : null; } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7bbad3bb72..1d9e2042ec 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -89,7 +89,7 @@ namespace osu.Game public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild; internal EndpointConfiguration CreateEndpoints() => - UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); + UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); @@ -583,6 +583,6 @@ namespace osu.Game ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.Beatmap.ControlPointInfo; IClock IBeatSyncProvider.Clock => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track : (IClock)null; - ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; } } diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs index a6f412c00a..8cedd6b374 100644 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs @@ -46,8 +46,8 @@ namespace osu.Game.Overlays.Mods && !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(Mod)); } - protected override Colour4 BackgroundColour => incompatible.Value ? (Colour4)ColourProvider.Background6 : base.BackgroundColour; - protected override Colour4 ForegroundColour => incompatible.Value ? (Colour4)ColourProvider.Background5 : base.ForegroundColour; + protected override Colour4 BackgroundColour => incompatible.Value ? ColourProvider.Background6 : base.BackgroundColour; + protected override Colour4 ForegroundColour => incompatible.Value ? ColourProvider.Background5 : base.ForegroundColour; protected override void UpdateState() { diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index c51e6baa0d..3f788d10e3 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -442,7 +442,7 @@ namespace osu.Game.Overlays.Mods case ModType.DifficultyIncrease: case ModType.Automation: return hotkeyStyle == ModSelectHotkeyStyle.Sequential - ? (IModHotkeyHandler)SequentialModHotkeyHandler.Create(ModType) + ? SequentialModHotkeyHandler.Create(ModType) : new ClassicModHotkeyHandler(allowIncompatibleSelection); default: diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 4d14a31189..02eb395bd9 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -216,9 +216,9 @@ namespace osu.Game.Overlays.Mods base.OnMouseUp(e); } - protected virtual Colour4 BackgroundColour => Active.Value ? activeColour.Darken(0.3f) : (Colour4)ColourProvider.Background3; - protected virtual Colour4 ForegroundColour => Active.Value ? activeColour : (Colour4)ColourProvider.Background2; - protected virtual Colour4 TextColour => Active.Value ? (Colour4)ColourProvider.Background6 : Colour4.White; + protected virtual Colour4 BackgroundColour => Active.Value ? activeColour.Darken(0.3f) : ColourProvider.Background3; + protected virtual Colour4 ForegroundColour => Active.Value ? activeColour : ColourProvider.Background2; + protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White; protected virtual void UpdateState() { diff --git a/osu.Game/Rulesets/EFRulesetInfo.cs b/osu.Game/Rulesets/EFRulesetInfo.cs index 3c671496de..bd67bdb93c 100644 --- a/osu.Game/Rulesets/EFRulesetInfo.cs +++ b/osu.Game/Rulesets/EFRulesetInfo.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets public int OnlineID { get => ID ?? -1; - set => ID = value >= 0 ? value : (int?)null; + set => ID = value >= 0 ? value : null; } #endregion diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 2285715564..ddc121eb5b 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Objects } public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null) - : this(controlPoints.Select((c, i) => new PathControlPoint(c, i == 0 ? (PathType?)type : null)).ToArray(), expectedDistance) + : this(controlPoints.Select((c, i) => new PathControlPoint(c, i == 0 ? type : null)).ToArray(), expectedDistance) { } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 09a090483d..020bae1c42 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Replays CurrentTime = Math.Clamp(time, frameStart, frameEnd); // In an important section, a mid-frame time cannot be used and a null is returned instead. - return inImportantSection && frameStart < time && time < frameEnd ? null : (double?)CurrentTime; + return inImportantSection && frameStart < time && time < frameEnd ? null : CurrentTime; } private double getFrameTime(int index) diff --git a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs index ec1e621491..e14354222b 100644 --- a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs +++ b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs @@ -24,6 +24,6 @@ namespace osu.Game.Screens.Edit.Components.Menus Action.Value = () => difficultyChangeFunc.Invoke(beatmapInfo); } - public override IconUsage? GetIconForState(bool state) => state ? (IconUsage?)FontAwesome.Solid.Check : null; + public override IconUsage? GetIconForState(bool state) => state ? FontAwesome.Solid.Check : null; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 847c96b762..08fc8383c3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } private static string? getCommonBank(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; - private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null; + private static int? getCommonVolume(SampleControlPoint[] relevantControlPoints) => relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? relevantControlPoints.First().SampleVolume : null; private void updateBankFor(IEnumerable objects, string? newBank) { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7a9dae33b4..20bdf75b7d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -908,6 +908,6 @@ namespace osu.Game.Screens.Edit ControlPointInfo IBeatSyncProvider.ControlPoints => editorBeatmap.ControlPointInfo; IClock IBeatSyncProvider.Clock => clock; - ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 0994a7d9bb..cd37c541ec 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -255,7 +255,7 @@ namespace osu.Game.Screens.Play ControlPointInfo IBeatSyncProvider.ControlPoints => beatmap.Beatmap.ControlPointInfo; IClock IBeatSyncProvider.Clock => GameplayClock; - ChannelAmplitudes? IBeatSyncProvider.Amplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : null; private class HardwareCorrectionOffsetClock : FramedOffsetClock { diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 73a835d67f..0400e2c938 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -92,8 +92,8 @@ namespace osu.Game.Screens.Select.Carousel var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) - .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) + ? beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset) + .Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Last().BeatmapInfo.Ruleset)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } } diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 6754b65129..c9d4dc7811 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -448,14 +448,14 @@ namespace osu.Game.Screens.Utility mainArea.AddRange(new[] { - new LatencyArea(Key.Number1, betterSide == 1 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : (int?)null) + new LatencyArea(Key.Number1, betterSide == 1 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : null) { Width = 0.5f, VisualMode = { BindTarget = VisualMode }, IsActiveArea = { Value = true }, ReportUserBest = () => recordResult(betterSide == 0), }, - new LatencyArea(Key.Number2, betterSide == 0 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : (int?)null) + new LatencyArea(Key.Number2, betterSide == 0 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : null) { Width = 0.5f, VisualMode = { BindTarget = VisualMode }, diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 20232012f6..6fc9f60177 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -56,7 +56,7 @@ namespace osu.Game.Storyboards { var first = Alpha.Commands.FirstOrDefault(); - return first?.StartValue == 0 ? first.StartTime : (double?)null; + return first?.StartValue == 0 ? first.StartTime : null; } } From 224c23df77c0430f78676377b8d155fb5375eb2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 14:58:22 +0900 Subject: [PATCH 136/363] Downgrade to v9 to support iOS toolchain --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 1f59f19eaa..235feea8ce 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@  - 10.0 + 9.0 true enable From 545c04aaf5acfed70065131865821d9ce305dd64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 16:18:51 +0900 Subject: [PATCH 137/363] Fix dangerous `File.Copy` causing intermittent realm migration test failures Resolves an issue I've been able to locally reproduce on windows. Basically, the `File.Copy` would begin while realm was blocking. The "restore" operation is posted to the `SynchronizationContext` to run on next update call, but in the mean time the copy would begin, causing a conflict of interest. Very dangerous. Only really noticeable on windows. --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 7d5f0bcd0c..a803974d30 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -254,7 +254,7 @@ namespace osu.Game.Tests.NonVisual Assert.That(File.Exists(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME))); Directory.CreateDirectory(customPath2); - File.Copy(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME), Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME)); + File.WriteAllText(Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME), "I am a text"); // Fails because file already exists. Assert.Throws(() => osu.Migrate(customPath2)); From 453b77e0dcac7c0f6742f5d51f046dd535abaf9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 16:18:51 +0900 Subject: [PATCH 138/363] Fix dangerous `File.Copy` causing intermittent realm migration test failures Resolves an issue I've been able to locally reproduce on windows. Basically, the `File.Copy` would begin while realm was blocking. The "restore" operation is posted to the `SynchronizationContext` to run on next update call, but in the mean time the copy would begin, causing a conflict of interest. Very dangerous. Only really noticeable on windows. --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 7d5f0bcd0c..a803974d30 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -254,7 +254,7 @@ namespace osu.Game.Tests.NonVisual Assert.That(File.Exists(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME))); Directory.CreateDirectory(customPath2); - File.Copy(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME), Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME)); + File.WriteAllText(Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME), "I am a text"); // Fails because file already exists. Assert.Throws(() => osu.Migrate(customPath2)); From 0755430006d582dded0f6a01fcef137a95e666c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 16:53:04 +0900 Subject: [PATCH 139/363] Use `AddOnce` for update calls --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 5e468e975a..7cab868e28 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -64,13 +64,13 @@ namespace osu.Game.Beatmaps { base.LoadComplete(); - currentRuleset.BindValueChanged(_ => updateTrackedBindables()); + currentRuleset.BindValueChanged(_ => Scheduler.AddOnce(updateTrackedBindables)); currentMods.BindValueChanged(mods => { modSettingChangeTracker?.Dispose(); - updateTrackedBindables(); + Scheduler.AddOnce(updateTrackedBindables); modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); modSettingChangeTracker.SettingChanged += _ => From c1791276700bfcb0d368d44321063343d8263d0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 16:53:14 +0900 Subject: [PATCH 140/363] Remove unused bindable retrieval flow --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 35 ++------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 7cab868e28..ef0fa36b16 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -89,7 +89,9 @@ namespace osu.Game.Beatmaps /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated). public IBindable GetBindableDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) { - var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); + var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); + + updateBindable(bindable, currentRuleset.Value, currentMods.Value, cancellationToken); lock (bindableUpdateLock) trackedBindables.Add(bindable); @@ -97,21 +99,6 @@ namespace osu.Game.Beatmaps return bindable; } - /// - /// Retrieves a bindable containing the star difficulty of a with a given and combination. - /// - /// - /// The bindable will not update to follow the currently-selected ruleset and mods or its settings. - /// - /// The to get the difficulty of. - /// The to get the difficulty with. If null, the 's ruleset is used. - /// The s to get the difficulty with. If null, no mods will be assumed. - /// An optional which stops updating the star difficulty for the given . - /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state. - public IBindable GetBindableDifficulty(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo, IEnumerable? mods, - CancellationToken cancellationToken = default) - => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); - /// /// Retrieves the difficulty of a . /// @@ -200,22 +187,6 @@ namespace osu.Game.Beatmaps } } - /// - /// Creates a new and triggers an initial value update. - /// - /// The that star difficulty should correspond to. - /// The initial to get the difficulty with. - /// The initial s to get the difficulty with. - /// An optional which stops updating the star difficulty for the given . - /// The . - private BindableStarDifficulty createBindable(IBeatmapInfo beatmapInfo, IRulesetInfo? initialRulesetInfo, IEnumerable? initialMods, - CancellationToken cancellationToken) - { - var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken); - updateBindable(bindable, initialRulesetInfo, initialMods, cancellationToken); - return bindable; - } - /// /// Updates the value of a with a given ruleset + mods. /// From 26c5b59f6d55976d36a6007aecab4b1e7fb9f39a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 14:39:47 +0200 Subject: [PATCH 141/363] Replace usages of `string.To{Lower,Upper}()` --- CodeAnalysis/BannedSymbols.txt | 2 ++ osu.Game.Rulesets.Catch/CatchSkinComponent.cs | 2 +- osu.Game.Rulesets.Mania/ManiaSkinComponent.cs | 2 +- osu.Game.Rulesets.Osu/OsuSkinComponent.cs | 2 +- .../Skinning/TestSceneDrawableTaikoMascot.cs | 6 +++--- osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs | 4 ++-- osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs | 2 +- osu.Game.Tournament/Models/TournamentTeam.cs | 4 ++-- .../Screens/Ladder/Components/DrawableTournamentRound.cs | 4 ++-- osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs | 2 +- .../Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs | 2 +- osu.Game/Database/IModelImporter.cs | 2 +- osu.Game/Database/ModelManager.cs | 2 +- osu.Game/Database/RealmArchiveModelImporter.cs | 2 +- osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs | 2 +- osu.Game/Online/API/Requests/GetUserRequest.cs | 2 +- osu.Game/Online/Leaderboards/UserTopScoreContainer.cs | 2 +- osu.Game/Overlays/Chat/DaySeparator.cs | 3 ++- osu.Game/Overlays/News/NewsCard.cs | 3 ++- osu.Game/Overlays/TabControlOverlayHeader.cs | 6 +++--- osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs | 2 +- .../OnlinePlay/Match/Components/RoomSettingsOverlay.cs | 2 +- osu.Game/Screens/Select/FilterQueryParser.cs | 2 +- 25 files changed, 35 insertions(+), 31 deletions(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 77197c4c51..8b5431e2d6 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -17,3 +17,5 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks. M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever. +M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. +M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs index 0fcdd34ca3..e79da667da 100644 --- a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs +++ b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Catch protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME; - protected override string ComponentName => Component.ToString().ToLower(); + protected override string ComponentName => Component.ToString().ToLowerInvariant(); } } diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs index b709f85523..21b362df00 100644 --- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs +++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME; - protected override string ComponentName => Component.ToString().ToLower(); + protected override string ComponentName => Component.ToString().ToLowerInvariant(); } public enum ManiaSkinComponents diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponent.cs b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs index 82c4005c5e..0abaf2c924 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponent.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Osu protected override string RulesetPrefix => OsuRuleset.SHORT_NAME; - protected override string ComponentName => Component.ToString().ToLower(); + protected override string ComponentName => Component.ToString().ToLowerInvariant(); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index 1bf6c0560a..ef95358d34 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning createDrawableRuleset(); assertStateAfterResult(new JudgementResult(new Swell(), new TaikoSwellJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Clear); - AddUntilStep($"state reverts to {expectedStateAfterClear.ToString().ToLower()}", () => allMascotsIn(expectedStateAfterClear)); + AddUntilStep($"state reverts to {expectedStateAfterClear.ToString().ToLowerInvariant()}", () => allMascotsIn(expectedStateAfterClear)); } private void setBeatmap(bool kiai = false) @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { TaikoMascotAnimationState[] mascotStates = null; - AddStep($"{judgementResult.Type.ToString().ToLower()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}", + AddStep($"{judgementResult.Type.ToString().ToLowerInvariant()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}", () => { applyNewResult(judgementResult); @@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Schedule(() => mascotStates = animatedMascots.Select(mascot => mascot.State.Value).ToArray()); }); - AddAssert($"state is {expectedState.ToString().ToLower()}", () => mascotStates.All(state => state == expectedState)); + AddAssert($"state is {expectedState.ToString().ToLowerInvariant()}", () => mascotStates.All(state => state == expectedState)); } private void applyNewResult(JudgementResult judgementResult) diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs index 676a3d4bc3..63314a6822 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Taiko protected override string RulesetPrefix => TaikoRuleset.SHORT_NAME; - protected override string ComponentName => Component.ToString().ToLower(); + protected override string ComponentName => Component.ToString().ToLowerInvariant(); } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs index bacd9b41f8..26a37fc464 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs @@ -139,10 +139,10 @@ namespace osu.Game.Rulesets.Taiko.UI private static Texture getAnimationFrame(ISkin skin, TaikoMascotAnimationState state, int frameIndex) { - var texture = skin.GetTexture($"pippidon{state.ToString().ToLower()}{frameIndex}"); + var texture = skin.GetTexture($"pippidon{state.ToString().ToLowerInvariant()}{frameIndex}"); if (frameIndex == 0 && texture == null) - texture = skin.GetTexture($"pippidon{state.ToString().ToLower()}"); + texture = skin.GetTexture($"pippidon{state.ToString().ToLowerInvariant()}"); return texture; } diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index f9c4985219..c4e8bbfccf 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -192,7 +192,7 @@ namespace osu.Game.Tests.Gameplay AddStep("apply perfect hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect })); AddAssert("not failed", () => !processor.HasFailed); - AddStep($"apply {resultApplied.ToString().ToLower()} hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = resultApplied })); + AddStep($"apply {resultApplied.ToString().ToLowerInvariant()} hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = resultApplied })); AddAssert("failed", () => processor.HasFailed); } diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 9dbe23b4b3..ac57f748da 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -66,14 +66,14 @@ namespace osu.Game.Tournament.Models { // use a sane default flag name based on acronym. if (val.OldValue.StartsWith(FlagName.Value, StringComparison.InvariantCultureIgnoreCase)) - FlagName.Value = val.NewValue.Length >= 2 ? val.NewValue?.Substring(0, 2).ToUpper() : string.Empty; + FlagName.Value = val.NewValue.Length >= 2 ? val.NewValue?.Substring(0, 2).ToUpperInvariant() : string.Empty; }; FullName.ValueChanged += val => { // use a sane acronym based on full name. if (val.OldValue.StartsWith(Acronym.Value, StringComparison.InvariantCultureIgnoreCase)) - Acronym.Value = val.NewValue.Length >= 3 ? val.NewValue?.Substring(0, 3).ToUpper() : string.Empty; + Acronym.Value = val.NewValue.Length >= 3 ? val.NewValue?.Substring(0, 3).ToUpperInvariant() : string.Empty; }; } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs index ac196130d6..544725996a 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs @@ -49,10 +49,10 @@ namespace osu.Game.Tournament.Screens.Ladder.Components }; name = round.Name.GetBoundCopy(); - name.BindValueChanged(n => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpper(), true); + name.BindValueChanged(n => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpperInvariant(), true); description = round.Description.GetBoundCopy(); - description.BindValueChanged(n => textDescription.Text = round.Description.Value?.ToUpper(), true); + description.BindValueChanged(n => textDescription.Text = round.Description.Value?.ToUpperInvariant(), true); } } } diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index df6e8e816e..925c697346 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -198,7 +198,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro { row.Add(new Sprite { - Texture = textures.Get($"Mods/{mods.ToLower()}"), + Texture = textures.Get($"Mods/{mods.ToLowerInvariant()}"), Scale = new Vector2(0.5f) }); } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs index 78481ac27a..bc0fcb92bb 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs @@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons }; favouriteRequest.Failure += e => { - Logger.Error(e, $"Failed to {actionType.ToString().ToLower()} beatmap: {e.Message}"); + Logger.Error(e, $"Failed to {actionType.ToString().ToLowerInvariant()} beatmap: {e.Message}"); Enabled.Value = true; }; diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index 26342cb5fe..ebb8be39ef 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -26,7 +26,7 @@ namespace osu.Game.Database /// /// A user displayable name for the model type associated with this manager. /// - string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; + string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLowerInvariant()}"; /// /// Fired when the user requests to view the resulting import. diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs index e0c3dccf57..9603412178 100644 --- a/osu.Game/Database/ModelManager.cs +++ b/osu.Game/Database/ModelManager.cs @@ -201,6 +201,6 @@ namespace osu.Game.Database public Action? PostNotification { get; set; } - public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; + public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLowerInvariant()}"; } } diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 27bfaf05be..a3349d9918 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -549,6 +549,6 @@ namespace osu.Game.Database yield return f.Filename; } - public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; + public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLowerInvariant()}"; } } diff --git a/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs b/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs index 75b0cf8027..2d8a8b3b61 100644 --- a/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs @@ -26,7 +26,7 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.AddParameter("spotlight", spotlight.ToString()); - req.AddParameter("filter", sort.ToString().ToLower()); + req.AddParameter("filter", sort.ToString().ToLowerInvariant()); return req; } diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index ab45e8e48f..7dcf75950e 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -45,7 +45,7 @@ namespace osu.Game.Online.API.Requests Ruleset = ruleset; } - protected override string Target => Lookup != null ? $@"users/{Lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}"; + protected override string Target => Lookup != null ? $@"users/{Lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLowerInvariant()}" : $@"me/{Ruleset?.ShortName}"; private enum LookupType { diff --git a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs index df8717fe6d..2d2d82821c 100644 --- a/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Online/Leaderboards/UserTopScoreContainer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Online.Leaderboards { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = @"your personal best".ToUpper(), + Text = @"your personal best".ToUpperInvariant(), Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), }, scoreContainer = new Container diff --git a/osu.Game/Overlays/Chat/DaySeparator.cs b/osu.Game/Overlays/Chat/DaySeparator.cs index c7961d1a39..be0b53785c 100644 --- a/osu.Game/Overlays/Chat/DaySeparator.cs +++ b/osu.Game/Overlays/Chat/DaySeparator.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -79,7 +80,7 @@ namespace osu.Game.Overlays.Chat { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(), + Text = time.ToLocalTime().ToLocalisableString(@"dd MMMM yyyy").ToUpper(), Font = OsuFont.Torus.With(size: TextSize, weight: FontWeight.SemiBold), Colour = colourProvider?.Content1 ?? Colour4.White, }, diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index 69094a0df7..eb76522e11 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -146,7 +147,7 @@ namespace osu.Game.Overlays.News }, new OsuSpriteText { - Text = date.ToString("d MMM yyyy").ToUpper(), + Text = date.ToLocalisableString(@"d MMM yyyy").ToUpper(), Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), Margin = new MarginPadding { diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index 4e32afb86f..84a5bf1144 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -109,15 +109,15 @@ namespace osu.Game.Overlays : base(value) { if (!(Value is Enum enumValue)) - Text.Text = Value.ToString().ToLower(); + Text.Text = Value.ToString().ToLowerInvariant(); else { var localisableDescription = enumValue.GetLocalisableDescription(); string nonLocalisableDescription = enumValue.GetDescription(); - // If localisable == non-localisable, then we must have a basic string, so .ToLower() is used. + // If localisable == non-localisable, then we must have a basic string, so .ToLowerInvariant() is used. Text.Text = localisableDescription.Equals(nonLocalisableDescription) - ? nonLocalisableDescription.ToLower() + ? nonLocalisableDescription.ToLowerInvariant() : localisableDescription; } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs index cb6c4a9928..00b1ca9dc9 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckMutedObjects.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (edgeType == EdgeType.None) yield break; - string postfix = hitObject is IHasDuration ? edgeType.ToString().ToLower() : null; + string postfix = hitObject is IHasDuration ? edgeType.ToString().ToLowerInvariant() : null; if (maxVolume <= muted_threshold) { diff --git a/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs index a461f63d2d..2a222aece0 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckTooShortAudioFiles.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Edit.Checks } } - private bool hasAudioExtension(string filename) => audioExtensions.Any(filename.ToLower().EndsWith); + private bool hasAudioExtension(string filename) => audioExtensions.Any(filename.ToLowerInvariant().EndsWith); private bool probablyHasAudioData(Stream data) => data.Length > min_bytes_threshold; public class IssueTemplateTooShort : IssueTemplate diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs index c295e9f85e..71447b15e2 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 12), - Text = title.ToUpper(), + Text = title.ToUpperInvariant(), }, content = new Container { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 92f707ae3e..03b72bf5e9 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Select { foreach (Match match in query_syntax_regex.Matches(query)) { - string key = match.Groups["key"].Value.ToLower(); + string key = match.Groups["key"].Value.ToLowerInvariant(); var op = parseOperator(match.Groups["op"].Value); string value = match.Groups["value"].Value; From 2ae48d5a87f3a71c67742869973d452106697e3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 17:46:46 +0900 Subject: [PATCH 142/363] Rename incorrect parameter name in `BeatmapManager.GetWorkingBeatmap` --- osu.Game/Beatmaps/BeatmapManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 816808638f..73c879226b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -418,22 +418,22 @@ namespace osu.Game.Beatmaps #region Implementation of IWorkingBeatmapCache - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? importedBeatmap) + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? beatmapInfo) { // Detached sets don't come with files. // If we seem to be missing files, now is a good time to re-fetch. - if (importedBeatmap?.BeatmapSet?.Files.Count == 0) + if (beatmapInfo?.BeatmapSet?.Files.Count == 0) { Realm.Run(r => { - var refetch = r.Find(importedBeatmap.ID)?.Detach(); + var refetch = r.Find(beatmapInfo.ID)?.Detach(); if (refetch != null) - importedBeatmap = refetch; + beatmapInfo = refetch; }); } - return workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); + return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); } void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo); From 823b3c1c0fcc74b4260c7ab09bdf13468f8984e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 19:04:29 +0900 Subject: [PATCH 143/363] Ensure `WorkingBeatmap` is always using a detached instance --- osu.Game/Beatmaps/BeatmapManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 73c879226b..670dba14ec 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -422,7 +422,7 @@ namespace osu.Game.Beatmaps { // Detached sets don't come with files. // If we seem to be missing files, now is a good time to re-fetch. - if (beatmapInfo?.BeatmapSet?.Files.Count == 0) + if (beatmapInfo?.IsManaged == true || beatmapInfo?.BeatmapSet?.Files.Count == 0) { Realm.Run(r => { @@ -433,6 +433,8 @@ namespace osu.Game.Beatmaps }); } + Debug.Assert(beatmapInfo?.IsManaged != true); + return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); } From 06d59b717c69ed873ca6a65cd5d02b45777f1fa4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:39:53 +0900 Subject: [PATCH 144/363] Move beatmap processing tasks to new `BeatmapUpdater` class --- .../Database/BeatmapImporterTests.cs | 58 +++++++------- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 8 +- osu.Game/Beatmaps/BeatmapImporter.cs | 70 ++--------------- osu.Game/Beatmaps/BeatmapManager.cs | 13 +++- osu.Game/Beatmaps/BeatmapUpdater.cs | 78 +++++++++++++++++++ osu.Game/OsuGameBase.cs | 2 +- 6 files changed, 130 insertions(+), 99 deletions(-) create mode 100644 osu.Game/Beatmaps/BeatmapUpdater.cs diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index be11476c73..f54dd3eb11 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -35,7 +35,8 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapImporter(storage, realm)) + var importer = new BeatmapImporter(storage, realm); + using (new RealmRulesetStore(realm, storage)) { var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); @@ -76,7 +77,8 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapImporter(storage, realm)) + var importer = new BeatmapImporter(storage, realm); + using (new RealmRulesetStore(realm, storage)) { var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); @@ -134,7 +136,8 @@ namespace osu.Game.Tests.Database var manager = new ModelManager(storage, realm); - using (var importer = new BeatmapImporter(storage, realm)) + var importer = new BeatmapImporter(storage, realm); + using (new RealmRulesetStore(realm, storage)) { Task.Run(async () => @@ -160,7 +163,8 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using (var importer = new BeatmapImporter(storage, realm)) + var importer = new BeatmapImporter(storage, realm); + using (new RealmRulesetStore(realm, storage)) { var imported = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); @@ -187,7 +191,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); await LoadOszIntoStore(importer, realm.Realm); @@ -199,7 +203,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -217,7 +221,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -231,7 +235,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? tempPath = TestResources.GetTestBeatmapForImport(); @@ -261,7 +265,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -281,7 +285,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -317,7 +321,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -366,7 +370,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -417,7 +421,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -465,7 +469,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -513,7 +517,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -548,7 +552,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var progressNotification = new ImportProgressNotification(); @@ -586,7 +590,7 @@ namespace osu.Game.Tests.Database Interlocked.Increment(ref loggedExceptionCount); }; - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -638,7 +642,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm, batchImport: true); @@ -665,7 +669,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realmFactory, storage) => { - using var importer = new BeatmapImporter(storage, realmFactory); + var importer = new BeatmapImporter(storage, realmFactory); using var store = new RealmRulesetStore(realmFactory, storage); var imported = await LoadOszIntoStore(importer, realmFactory.Realm); @@ -697,7 +701,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -724,7 +728,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var imported = await LoadOszIntoStore(importer, realm.Realm); @@ -750,7 +754,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); var metadata = new BeatmapMetadata @@ -798,7 +802,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -815,7 +819,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -851,7 +855,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -893,7 +897,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); @@ -944,7 +948,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealmAsync(async (realm, storage) => { - using var importer = new BeatmapImporter(storage, realm); + var importer = new BeatmapImporter(storage, realm); using var store = new RealmRulesetStore(realm, storage); string? temp = TestResources.GetTestBeatmapForImport(); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 1d33a895eb..0bf47141e4 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -212,17 +212,17 @@ namespace osu.Game.Tests.Online { } - protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue) + protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapUpdater beatmapUpdater) { - return new TestBeatmapImporter(this, storage, realm, onlineLookupQueue); + return new TestBeatmapImporter(this, storage, realm, beatmapUpdater); } internal class TestBeatmapImporter : BeatmapImporter { private readonly TestBeatmapManager testBeatmapManager; - public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue) - : base(storage, databaseAccess, beatmapOnlineLookupQueue) + public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapUpdater beatmapUpdater) + : base(storage, databaseAccess, beatmapUpdater) { this.testBeatmapManager = testBeatmapManager; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 237088036c..0620e5d9b1 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -6,10 +6,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; -using osu.Framework.Audio.Track; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; @@ -20,8 +18,6 @@ using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Models; using osu.Game.Rulesets; -using osu.Game.Rulesets.Objects; -using osu.Game.Skinning; using Realms; namespace osu.Game.Beatmaps @@ -30,18 +26,18 @@ namespace osu.Game.Beatmaps /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. /// [ExcludeFromDynamicCompile] - public class BeatmapImporter : RealmArchiveModelImporter, IDisposable + public class BeatmapImporter : RealmArchiveModelImporter { public override IEnumerable HandledExtensions => new[] { ".osz" }; protected override string[] HashableFileTypes => new[] { ".osu" }; - private readonly BeatmapOnlineLookupQueue? onlineLookupQueue; + private readonly BeatmapUpdater? beatmapUpdater; - public BeatmapImporter(Storage storage, RealmAccess realm, BeatmapOnlineLookupQueue? onlineLookupQueue = null) + public BeatmapImporter(Storage storage, RealmAccess realm, BeatmapUpdater? beatmapUpdater = null) : base(storage, realm) { - this.onlineLookupQueue = onlineLookupQueue; + this.beatmapUpdater = beatmapUpdater; } protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; @@ -65,8 +61,7 @@ namespace osu.Game.Beatmaps bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); - onlineLookupQueue?.Update(beatmapSet); - + // TODO: this may no longer be valid as we aren't doing an online population at this point. // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) { @@ -85,6 +80,8 @@ namespace osu.Game.Beatmaps // If this is ever an issue, we can consider marking as pending delete but not resetting the IDs (but care will be required for // beatmaps, which don't have their own `DeletePending` state). + beatmapUpdater?.Process(beatmapSet, realm); + if (beatmapSet.OnlineID > 0) { var existingSetWithSameOnlineID = realm.All().SingleOrDefault(b => b.OnlineID == beatmapSet.OnlineID); @@ -278,64 +275,11 @@ namespace osu.Game.Beatmaps MD5Hash = memoryStream.ComputeMD5Hash(), }; - updateBeatmapStatistics(beatmap, decoded); - beatmaps.Add(beatmap); } } return beatmaps; } - - private void updateBeatmapStatistics(BeatmapInfo beatmap, IBeatmap decoded) - { - var rulesetInstance = ((IRulesetInfo)beatmap.Ruleset).CreateInstance(); - - 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.Length = calculateLength(decoded); - beatmap.BPM = 60000 / decoded.GetMostCommonBeatLength(); - } - - private double calculateLength(IBeatmap b) - { - if (!b.HitObjects.Any()) - return 0; - - var lastObject = b.HitObjects.Last(); - - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - double endTime = lastObject.GetEndTime(); - double startTime = b.HitObjects.First().StartTime; - - return endTime - startTime; - } - - public void Dispose() - { - onlineLookupQueue?.Dispose(); - } - - /// - /// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation. - /// - private class DummyConversionBeatmap : WorkingBeatmap - { - private readonly IBeatmap beatmap; - - public DummyConversionBeatmap(IBeatmap beatmap) - : base(beatmap.BeatmapInfo, null) - { - this.beatmap = beatmap; - } - - protected override IBeatmap GetBeatmap() => beatmap; - protected override Texture? GetBackground() => null; - protected override Track? GetBeatmapTrack() => null; - protected internal override ISkin? GetSkin() => null; - public override Stream? GetStream(string storagePath) => null; - } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 670dba14ec..ee7fcb5b47 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -42,9 +42,10 @@ namespace osu.Game.Beatmaps private readonly WorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue? onlineBeatmapLookupQueue; + private readonly BeatmapUpdater? beatmapUpdater; public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, - WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false) + WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false) : base(storage, realm) { if (performOnlineLookups) @@ -52,14 +53,18 @@ namespace osu.Game.Beatmaps if (api == null) throw new ArgumentNullException(nameof(api), "API must be provided if online lookups are required."); + if (difficultyCache == null) + throw new ArgumentNullException(nameof(difficultyCache), "Difficulty cache must be provided if online lookups are required."); + onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); + beatmapUpdater = new BeatmapUpdater(this, onlineBeatmapLookupQueue, difficultyCache); } var userResources = new RealmFileStore(realm, storage).Store; BeatmapTrackStore = audioManager.GetTrackStore(userResources); - beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, onlineBeatmapLookupQueue); + beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, beatmapUpdater); beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); @@ -71,8 +76,8 @@ namespace osu.Game.Beatmaps return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); } - protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) => - new BeatmapImporter(storage, realm, onlineLookupQueue); + protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapUpdater? beatmapUpdater) => + new BeatmapImporter(storage, realm, beatmapUpdater); /// /// Create a new beatmap set, backed by a model, diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs new file mode 100644 index 0000000000..8ccaf893a7 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Extensions; +using osu.Game.Database; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Beatmaps +{ + /// + /// Handles all processing required to ensure a local beatmap is in a consistent state with any changes. + /// + public class BeatmapUpdater + { + private readonly BeatmapManager beatmapManager; + private readonly BeatmapOnlineLookupQueue onlineLookupQueue; + private readonly BeatmapDifficultyCache difficultyCache; + + public BeatmapUpdater(BeatmapManager beatmapManager, BeatmapOnlineLookupQueue onlineLookupQueue, BeatmapDifficultyCache difficultyCache) + { + this.beatmapManager = beatmapManager; + this.onlineLookupQueue = onlineLookupQueue; + this.difficultyCache = difficultyCache; + } + + /// + /// Queue a beatmap for background processing. + /// + public void Queue(Live beatmap) + { + // For now, just fire off a task. + // TODO: Add actual queueing probably. + Task.Factory.StartNew(() => beatmap.PerformRead(Process)); + } + + /// + /// Run all processing on a beatmap immediately. + /// + public void Process(BeatmapSetInfo beatmapSet) + { + beatmapSet.Realm.Write(() => + { + onlineLookupQueue.Update(beatmapSet); + + foreach (var beatmap in beatmapSet.Beatmaps) + { + var working = beatmapManager.GetWorkingBeatmap(beatmap); + + // Because we aren't guaranteed all processing will happen on this thread, it's very hard to use the live realm object. + // This can be fixed by adding a synchronous flow to `BeatmapDifficultyCache`. + var detachedBeatmap = beatmap.Detach(); + + beatmap.StarRating = difficultyCache.GetDifficultyAsync(detachedBeatmap).GetResultSafely()?.Stars ?? 0; + beatmap.Length = calculateLength(working.Beatmap); + beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); + } + }); + } + + private double calculateLength(IBeatmap b) + { + if (!b.HitObjects.Any()) + return 0; + + var lastObject = b.HitObjects.Last(); + + //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). + double endTime = lastObject.GetEndTime(); + double startTime = b.HitObjects.First().StartTime; + + return endTime - startTime; + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7bbad3bb72..403c5d1b49 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -272,7 +272,7 @@ namespace osu.Game // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, difficultyCache, LocalConfig)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API)); From fe570d8052356d3c5a64410719e3ea716a920f17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:57:57 +0900 Subject: [PATCH 145/363] Queue beatmaps for update after editing --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ee7fcb5b47..1b85207f22 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -324,6 +324,10 @@ namespace osu.Game.Beatmaps workingBeatmapCache.Invalidate(beatmapInfo); + Debug.Assert(beatmapInfo.BeatmapSet != null); + + beatmapUpdater?.Queue(Realm.Run(r => r.Find(setInfo.ID).ToLive(Realm))); + static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo) { var metadata = beatmapInfo.Metadata; From 021b16f2f3635469fd5b9673424582dc3dfafebf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 19:25:18 +0900 Subject: [PATCH 146/363] Ensure `WorkingBeatmap` is invalidated after update --- osu.Game/Beatmaps/BeatmapUpdater.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 8ccaf893a7..7689f4d007 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -16,13 +16,13 @@ namespace osu.Game.Beatmaps /// public class BeatmapUpdater { - private readonly BeatmapManager beatmapManager; + private readonly IWorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue onlineLookupQueue; private readonly BeatmapDifficultyCache difficultyCache; - public BeatmapUpdater(BeatmapManager beatmapManager, BeatmapOnlineLookupQueue onlineLookupQueue, BeatmapDifficultyCache difficultyCache) + public BeatmapUpdater(IWorkingBeatmapCache workingBeatmapCache, BeatmapOnlineLookupQueue onlineLookupQueue, BeatmapDifficultyCache difficultyCache) { - this.beatmapManager = beatmapManager; + this.workingBeatmapCache = workingBeatmapCache; this.onlineLookupQueue = onlineLookupQueue; this.difficultyCache = difficultyCache; } @@ -48,17 +48,19 @@ namespace osu.Game.Beatmaps foreach (var beatmap in beatmapSet.Beatmaps) { - var working = beatmapManager.GetWorkingBeatmap(beatmap); - // Because we aren't guaranteed all processing will happen on this thread, it's very hard to use the live realm object. // This can be fixed by adding a synchronous flow to `BeatmapDifficultyCache`. var detachedBeatmap = beatmap.Detach(); beatmap.StarRating = difficultyCache.GetDifficultyAsync(detachedBeatmap).GetResultSafely()?.Stars ?? 0; + + var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); beatmap.Length = calculateLength(working.Beatmap); beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); } }); + + workingBeatmapCache.Invalidate(beatmapSet); } private double calculateLength(IBeatmap b) From 6999933d33a4289f4dd5645079f2fb2d3f8f04e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 18:27:47 +0900 Subject: [PATCH 147/363] Split updater process into realm transaction and non-transaction --- osu.Game/Beatmaps/BeatmapManager.cs | 13 ++++++---- osu.Game/Beatmaps/BeatmapUpdater.cs | 39 +++++++++++++++-------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1b85207f22..effa0ab094 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -319,15 +319,18 @@ namespace osu.Game.Beatmaps AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo)); - Realm.Write(r => setInfo.CopyChangesToRealm(r.Find(setInfo.ID))); + Realm.Write(r => + { + var liveBeatmapSet = r.Find(setInfo.ID); + + setInfo.CopyChangesToRealm(liveBeatmapSet); + + beatmapUpdater?.Process(liveBeatmapSet, r); + }); } - workingBeatmapCache.Invalidate(beatmapInfo); - Debug.Assert(beatmapInfo.BeatmapSet != null); - beatmapUpdater?.Queue(Realm.Run(r => r.Find(setInfo.ID).ToLive(Realm))); - static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo) { var metadata = beatmapInfo.Metadata; diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 7689f4d007..a24407a734 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using osu.Framework.Extensions; using osu.Game.Database; using osu.Game.Rulesets.Objects; +using Realms; namespace osu.Game.Beatmaps { @@ -40,27 +41,27 @@ namespace osu.Game.Beatmaps /// /// Run all processing on a beatmap immediately. /// - public void Process(BeatmapSetInfo beatmapSet) + public void Process(BeatmapSetInfo beatmapSet) => beatmapSet.Realm.Write(r => Process(beatmapSet, r)); + + public void Process(BeatmapSetInfo beatmapSet, Realm realm) { - beatmapSet.Realm.Write(() => - { - onlineLookupQueue.Update(beatmapSet); - - foreach (var beatmap in beatmapSet.Beatmaps) - { - // Because we aren't guaranteed all processing will happen on this thread, it's very hard to use the live realm object. - // This can be fixed by adding a synchronous flow to `BeatmapDifficultyCache`. - var detachedBeatmap = beatmap.Detach(); - - beatmap.StarRating = difficultyCache.GetDifficultyAsync(detachedBeatmap).GetResultSafely()?.Stars ?? 0; - - var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); - beatmap.Length = calculateLength(working.Beatmap); - beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); - } - }); - + // Before we use below, we want to invalidate. workingBeatmapCache.Invalidate(beatmapSet); + + onlineLookupQueue.Update(beatmapSet); + + foreach (var beatmap in beatmapSet.Beatmaps) + { + // Because we aren't guaranteed all processing will happen on this thread, it's very hard to use the live realm object. + // This can be fixed by adding a synchronous flow to `BeatmapDifficultyCache`. + var detachedBeatmap = beatmap.Detach(); + + beatmap.StarRating = difficultyCache.GetDifficultyAsync(detachedBeatmap).GetResultSafely()?.Stars ?? 0; + + var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); + beatmap.Length = calculateLength(working.Beatmap); + beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); + } } private double calculateLength(IBeatmap b) From 66a01d1ed29a261a0b5a490eef1e63b0177d2a5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 19:48:46 +0900 Subject: [PATCH 148/363] Allow song select to refresh the global `WorkingBeatmap` after an external update --- osu.Game/Beatmaps/BeatmapManager.cs | 6 ++++++ osu.Game/Beatmaps/IWorkingBeatmapCache.cs | 4 ++++ osu.Game/Beatmaps/WorkingBeatmapCache.cs | 3 +++ osu.Game/Screens/Select/SongSelect.cs | 18 ++++++++++++++++++ 4 files changed, 31 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index effa0ab094..ff176484b6 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -453,6 +453,12 @@ namespace osu.Game.Beatmaps void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo); void IWorkingBeatmapCache.Invalidate(BeatmapInfo beatmapInfo) => workingBeatmapCache.Invalidate(beatmapInfo); + public event Action? OnInvalidated + { + add => workingBeatmapCache.OnInvalidated += value; + remove => workingBeatmapCache.OnInvalidated -= value; + } + public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); #endregion diff --git a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs index ad9cac6957..36c53fce99 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs @@ -3,6 +3,8 @@ #nullable disable +using System; + namespace osu.Game.Beatmaps { public interface IWorkingBeatmapCache @@ -25,5 +27,7 @@ namespace osu.Game.Beatmaps /// /// The beatmap info to invalidate any cached entries for. void Invalidate(BeatmapInfo beatmapInfo); + + event Action OnInvalidated; } } diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 4495fb8318..9d31c58709 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -76,10 +76,13 @@ namespace osu.Game.Beatmaps { Logger.Log($"Invalidating working beatmap cache for {info}"); workingCache.Remove(working); + OnInvalidated?.Invoke(working); } } } + public event Action OnInvalidated; + public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo) { if (beatmapInfo?.BeatmapSet == null) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 41fb55a856..d3635ea553 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -296,8 +296,24 @@ namespace osu.Game.Screens.Select base.LoadComplete(); modSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(ModSelect); + + beatmaps.OnInvalidated += workingBeatmapInvalidated; } + private void workingBeatmapInvalidated(WorkingBeatmap working) => Scheduler.AddOnce(w => + { + // The global beatmap may have already been updated (ie. by the editor). + // Only perform the actual switch if we still need to. + if (w == Beatmap.Value) + { + // Not sure if this refresh is required. + var beatmapInfo = beatmaps.QueryBeatmap(b => b.ID == w.BeatmapInfo.ID); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); + } + + updateComponentFromBeatmap(Beatmap.Value); + }, working); + /// /// Creates the buttons to be displayed in the footer. /// @@ -700,6 +716,8 @@ namespace osu.Game.Screens.Select music.TrackChanged -= ensureTrackLooping; modSelectOverlayRegistration?.Dispose(); + + beatmaps.OnInvalidated -= workingBeatmapInvalidated; } /// From 30b3973c9fcef6a8a1a308376815d5559eea3d65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 18:39:44 +0900 Subject: [PATCH 149/363] Difficulty cache invalidation flow --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 5 +++++ osu.Game/Beatmaps/BeatmapUpdater.cs | 2 ++ osu.Game/Database/MemoryCachingComponent.cs | 10 ++++++++++ 3 files changed, 17 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index ef0fa36b16..493de5bef4 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -81,6 +81,11 @@ namespace osu.Game.Beatmaps }, true); } + public void Invalidate(IBeatmapInfo beatmap) + { + base.Invalidate(lookup => lookup.BeatmapInfo.Equals(beatmap)); + } + /// /// Retrieves a bindable containing the star difficulty of a that follows the currently-selected ruleset and mods. /// diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index a24407a734..e6703dd8d0 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -61,6 +61,8 @@ namespace osu.Game.Beatmaps var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); beatmap.Length = calculateLength(working.Beatmap); beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); + + difficultyCache.Invalidate(beatmap); } } diff --git a/osu.Game/Database/MemoryCachingComponent.cs b/osu.Game/Database/MemoryCachingComponent.cs index 6e6d928dcc..215050460b 100644 --- a/osu.Game/Database/MemoryCachingComponent.cs +++ b/osu.Game/Database/MemoryCachingComponent.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; @@ -39,6 +40,15 @@ namespace osu.Game.Database return computed; } + protected void Invalidate(Func invalidationFunction) + { + foreach (var kvp in cache) + { + if (invalidationFunction(kvp.Key)) + cache.TryRemove(kvp.Key, out _); + } + } + protected bool CheckExists([NotNull] TLookup lookup, out TValue value) => cache.TryGetValue(lookup, out value); From 0c3d890f76588465df93aa2e80d57bc7199b4f17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 19:04:20 +0900 Subject: [PATCH 150/363] Fix reprocessing not working on import due to realm threading woes --- osu.Game/Beatmaps/BeatmapImporter.cs | 9 +++++++-- osu.Game/Beatmaps/BeatmapUpdater.cs | 17 +++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 0620e5d9b1..05c5a15505 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -80,8 +80,6 @@ namespace osu.Game.Beatmaps // If this is ever an issue, we can consider marking as pending delete but not resetting the IDs (but care will be required for // beatmaps, which don't have their own `DeletePending` state). - beatmapUpdater?.Process(beatmapSet, realm); - if (beatmapSet.OnlineID > 0) { var existingSetWithSameOnlineID = realm.All().SingleOrDefault(b => b.OnlineID == beatmapSet.OnlineID); @@ -99,6 +97,13 @@ namespace osu.Game.Beatmaps } } + protected override void PostImport(BeatmapSetInfo model, Realm realm) + { + base.PostImport(model, realm); + + beatmapUpdater?.Process(model); + } + private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm) { var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineID > 0).Select(b => b.OnlineID).ToList(); diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index e6703dd8d0..0397cfbea9 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -3,9 +3,9 @@ #nullable enable +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using osu.Framework.Extensions; using osu.Game.Database; using osu.Game.Rulesets.Objects; using Realms; @@ -52,17 +52,18 @@ namespace osu.Game.Beatmaps foreach (var beatmap in beatmapSet.Beatmaps) { - // Because we aren't guaranteed all processing will happen on this thread, it's very hard to use the live realm object. - // This can be fixed by adding a synchronous flow to `BeatmapDifficultyCache`. - var detachedBeatmap = beatmap.Detach(); + difficultyCache.Invalidate(beatmap); - beatmap.StarRating = difficultyCache.GetDifficultyAsync(detachedBeatmap).GetResultSafely()?.Stars ?? 0; + var working = workingBeatmapCache.GetWorkingBeatmap(beatmap.Detach()); + var ruleset = working.BeatmapInfo.Ruleset.CreateInstance(); - var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); + Debug.Assert(ruleset != null); + + var calculator = ruleset.CreateDifficultyCalculator(working); + + beatmap.StarRating = calculator.Calculate().StarRating; beatmap.Length = calculateLength(working.Beatmap); beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); - - difficultyCache.Invalidate(beatmap); } } From 7692bac35a27618c8c5b74a80923d862d2340159 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 21:02:14 +0900 Subject: [PATCH 151/363] Simplify refetch (and ensure to invalidate after processing) --- osu.Game/Beatmaps/BeatmapManager.cs | 22 +++++++++++++--------- osu.Game/Beatmaps/BeatmapUpdater.cs | 5 ++++- osu.Game/Screens/Edit/Editor.cs | 4 +--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ff176484b6..26f538f65b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -430,26 +430,30 @@ namespace osu.Game.Beatmaps #region Implementation of IWorkingBeatmapCache - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? beatmapInfo) + /// + /// Retrieve a instance for the provided + /// + /// The beatmap to lookup. + /// Whether to force a refetch from the database to ensure is up-to-date. + /// A instance correlating to the provided . + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, bool refetch = false) { // Detached sets don't come with files. // If we seem to be missing files, now is a good time to re-fetch. - if (beatmapInfo?.IsManaged == true || beatmapInfo?.BeatmapSet?.Files.Count == 0) + if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0) { - Realm.Run(r => - { - var refetch = r.Find(beatmapInfo.ID)?.Detach(); + workingBeatmapCache.Invalidate(beatmapInfo); - if (refetch != null) - beatmapInfo = refetch; - }); + Guid id = beatmapInfo.ID; + beatmapInfo = Realm.Run(r => r.Find(id)?.Detach()) ?? beatmapInfo; } - Debug.Assert(beatmapInfo?.IsManaged != true); + Debug.Assert(beatmapInfo.IsManaged != true); return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); } + WorkingBeatmap IWorkingBeatmapCache.GetWorkingBeatmap(BeatmapInfo beatmapInfo) => GetWorkingBeatmap(beatmapInfo); void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo); void IWorkingBeatmapCache.Invalidate(BeatmapInfo beatmapInfo) => workingBeatmapCache.Invalidate(beatmapInfo); diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 0397cfbea9..caef094701 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps { difficultyCache.Invalidate(beatmap); - var working = workingBeatmapCache.GetWorkingBeatmap(beatmap.Detach()); + var working = workingBeatmapCache.GetWorkingBeatmap(beatmap); var ruleset = working.BeatmapInfo.Ruleset.CreateInstance(); Debug.Assert(ruleset != null); @@ -65,6 +65,9 @@ namespace osu.Game.Beatmaps beatmap.Length = calculateLength(working.Beatmap); beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); } + + // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. + workingBeatmapCache.Invalidate(beatmapSet); } private double calculateLength(IBeatmap b) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7a9dae33b4..528a237ac4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -630,9 +630,7 @@ namespace osu.Game.Screens.Edit // To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend. // This is required as the editor makes its local changes via EditorBeatmap // (which are not propagated outwards to a potentially cached WorkingBeatmap). - ((IWorkingBeatmapCache)beatmapManager).Invalidate(Beatmap.Value.BeatmapInfo); - var refetchedBeatmapInfo = beatmapManager.QueryBeatmap(b => b.ID == Beatmap.Value.BeatmapInfo.ID); - var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(refetchedBeatmapInfo); + var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo, true); if (!(refetchedBeatmap is DummyWorkingBeatmap)) { From 2c5e5fed6f8edd6f0238c5c8af09b440d9421aca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 21:02:29 +0900 Subject: [PATCH 152/363] Add test coverage of star rating and difficulty updating on editor save --- .../Visual/Editing/TestSceneEditorSaving.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index e9bbf1e33d..bcf02cd814 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -130,6 +131,54 @@ namespace osu.Game.Tests.Visual.Editing !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT)); } + [Test] + public void TestLengthAndStarRatingUpdated() + { + WorkingBeatmap working = null; + double lastStarRating = 0; + double lastLength = 0; + + AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint())); + AddStep("Change to placement mode", () => InputManager.Key(Key.Number2)); + AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); + AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left)); + AddAssert("One hitobject placed", () => EditorBeatmap.HitObjects.Count == 1); + + SaveEditor(); + AddStep("Get working beatmap", () => working = Game.BeatmapManager.GetWorkingBeatmap(EditorBeatmap.BeatmapInfo, true)); + + AddAssert("Beatmap length is zero", () => working.BeatmapInfo.Length == 0); + checkDifficultyIncreased(); + + AddStep("Move forward", () => InputManager.Key(Key.Right)); + AddStep("Place another hitcircle", () => InputManager.Click(MouseButton.Left)); + AddAssert("Two hitobjects placed", () => EditorBeatmap.HitObjects.Count == 2); + + SaveEditor(); + AddStep("Get working beatmap", () => working = Game.BeatmapManager.GetWorkingBeatmap(EditorBeatmap.BeatmapInfo, true)); + + checkDifficultyIncreased(); + checkLengthIncreased(); + + void checkLengthIncreased() + { + AddStep("Beatmap length increased", () => + { + Assert.That(working.BeatmapInfo.Length, Is.GreaterThan(lastLength)); + lastLength = working.BeatmapInfo.Length; + }); + } + + void checkDifficultyIncreased() + { + AddStep("Beatmap difficulty increased", () => + { + Assert.That(working.BeatmapInfo.StarRating, Is.GreaterThan(lastStarRating)); + lastStarRating = working.BeatmapInfo.StarRating; + }); + } + } + [Test] public void TestExitWithoutSaveFromExistingBeatmap() { From 31a447fda0532fcf15f8c8251ca890c533386492 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 21:25:23 +0900 Subject: [PATCH 153/363] Update parameter discards --- osu.Desktop/DiscordRichPresence.cs | 2 +- osu.Desktop/Program.cs | 8 +++--- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 2 +- .../JuiceStreamPathTest.cs | 2 +- .../TestSceneHyperDash.cs | 2 +- .../Edit/CatchHitObjectComposer.cs | 6 ++--- .../Edit/CatchHitObjectUtils.cs | 4 +-- .../Edit/CatchSelectionHandler.cs | 2 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 ++--- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Difficulty/ManiaDifficultyCalculator.cs | 6 ++--- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 26 +++++++++---------- .../Mods/ManiaModRandom.cs | 2 +- .../Replays/ManiaAutoGenerator.cs | 4 +-- .../LegacyMainCirclePieceTest.cs | 2 +- .../Mods/TestSceneOsuModSpunOut.cs | 4 +-- .../Edit/OsuHitObjectComposer.cs | 4 +-- .../Mods/OsuModApproachDifferent.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 8 +++--- .../Mods/OsuModObjectScaleTween.cs | 8 +++--- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 8 +++--- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 8 +++--- .../Replays/OsuAutoGenerator.cs | 2 +- .../Scoring/OsuScoreProcessor.cs | 2 +- .../Skinning/Legacy/LegacyNewStyleSpinner.cs | 2 +- .../UI/ObjectOrderedHitPolicy.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- .../OsuHitObjectGenerationUtils_Reposition.cs | 4 +-- .../TestSceneTaikoSuddenDeath.cs | 2 +- .../Mods/TaikoModHidden.cs | 4 +-- osu.Game.Rulesets.Taiko/Objects/Hit.cs | 2 +- .../Objects/TaikoStrongableHitObject.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 8 +++--- .../Collections/IO/ImportCollectionsTest.cs | 2 +- osu.Game.Tests/Database/GeneralUsageTests.cs | 2 +- osu.Game.Tests/Database/RealmLiveTests.cs | 2 +- .../Skinning/LegacySkinTextureFallbackTest.cs | 2 +- .../TestSceneDrawableScrollingRuleset.cs | 4 +-- .../Visual/Gameplay/TestSceneFailAnimation.cs | 2 +- .../Visual/Gameplay/TestSceneFailJudgement.cs | 2 +- .../Visual/Gameplay/TestSceneSkinEditor.cs | 2 +- .../Gameplay/TestSceneSkinnableDrawable.cs | 6 ++--- .../Gameplay/TestSceneStoryboardWithOutro.cs | 4 +-- ...MultiplayerGameplayLeaderboardTestScene.cs | 2 +- .../TestSceneDrawableLoungeRoom.cs | 2 +- .../Multiplayer/TestSceneMatchStartControl.cs | 2 +- .../Visual/Online/TestSceneNewsSidebar.cs | 2 +- .../TestScenePlaylistsResultsScreen.cs | 4 +-- .../Visual/Settings/TestSceneSettingsPanel.cs | 2 +- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 8 +++--- .../TestSceneBeatSyncedContainer.cs | 8 +++--- .../TestSceneBeatmapListingSearchControl.cs | 6 ++--- .../TestSceneFirstRunSetupOverlay.cs | 2 +- .../TestSceneModSelectOverlay.cs | 2 +- osu.Game.Tournament/Components/DateTextBox.cs | 2 +- .../Components/DrawableTeamFlag.cs | 2 +- .../Components/DrawableTeamTitle.cs | 2 +- .../Components/DrawableTournamentTeam.cs | 2 +- .../Screens/Drawings/DrawingsScreen.cs | 2 +- .../Screens/Editors/LadderEditorScreen.cs | 2 +- .../Components/DrawableTournamentRound.cs | 4 +-- .../Screens/Ladder/LadderScreen.cs | 2 +- osu.Game.Tournament/TournamentGame.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 2 +- osu.Game.Tournament/TournamentSceneManager.cs | 4 +-- .../ControlPoints/ControlPointInfo.cs | 4 +-- .../Drawables/BundledBeatmapDownloader.cs | 2 +- .../Beatmaps/Formats/JsonBeatmapDecoder.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 +-- .../Formats/LegacyStoryboardDecoder.cs | 2 +- .../Beatmaps/Legacy/LegacyControlPointInfo.cs | 4 +-- osu.Game/Collections/BeatmapCollection.cs | 2 +- .../Collections/CollectionFilterDropdown.cs | 4 +-- osu.Game/Collections/CollectionManager.cs | 2 +- osu.Game/Database/RealmAccess.cs | 2 +- osu.Game/Database/RealmObjectExtensions.cs | 14 +++++----- osu.Game/Extensions/TaskExtensions.cs | 2 +- .../Markdown/OsuMarkdownContainer.cs | 2 +- osu.Game/Graphics/UserInterface/BarGraph.cs | 2 +- .../Graphics/UserInterface/LoadingLayer.cs | 4 +-- .../Graphics/UserInterface/StarCounter.cs | 2 +- .../Bindings/DatabasedKeyBindingContainer.cs | 2 +- osu.Game/Input/IdleTracker.cs | 10 +++---- osu.Game/Online/BeatmapDownloadTracker.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 2 +- osu.Game/Online/HubClientConnector.cs | 2 +- .../OnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- .../BeatmapListingFilterControl.cs | 6 ++--- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 2 +- .../BeatmapSet/Scores/ScoresContainer.cs | 2 +- osu.Game/Overlays/Comments/CommentEditor.cs | 2 +- .../Overlays/Comments/CommentsContainer.cs | 4 +-- .../Dashboard/Friends/FriendDisplay.cs | 2 +- osu.Game/Overlays/Login/LoginForm.cs | 2 +- .../Overlays/MedalSplash/DrawableMedal.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 ++--- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- .../Notifications/ProgressNotification.cs | 2 +- osu.Game/Overlays/OSD/TrackedSettingToast.cs | 2 +- .../Sections/Graphics/LayoutSettings.cs | 6 ++--- .../Sections/Maintenance/BeatmapSettings.cs | 10 +++---- .../Maintenance/CollectionsSettings.cs | 2 +- .../Sections/Maintenance/ScoreSettings.cs | 4 +-- .../Sections/Maintenance/SkinSettings.cs | 4 +-- .../Overlays/Settings/Sections/SkinSection.cs | 4 +-- .../Configuration/RulesetConfigManager.cs | 2 +- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 4 +-- .../Mods/DifficultyAdjustSettingsControl.cs | 4 +-- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 +-- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 26 +++++++++---------- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- .../TernaryButtons/DrawableTernaryButton.cs | 2 +- .../Summary/Parts/ControlPointPart.cs | 2 +- .../Summary/Parts/GroupVisualisation.cs | 8 +++--- .../Timelines/Summary/Parts/TimelinePart.cs | 2 +- .../Compose/Components/BeatDivisorControl.cs | 2 +- .../Compose/Components/BlueprintContainer.cs | 2 +- .../Components/EditorSelectionHandler.cs | 2 +- .../Compose/Components/SelectionHandler.cs | 2 +- .../Components/Timeline/SamplePointPiece.cs | 6 ++--- .../Timeline/TimelineControlPointDisplay.cs | 2 +- .../Timeline/TimelineControlPointGroup.cs | 2 +- .../Screens/Edit/Compose/ComposeScreen.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 4 +-- osu.Game/Screens/Edit/EditorBeatmapSkin.cs | 2 +- osu.Game/Screens/Edit/Setup/DesignSection.cs | 2 +- .../Screens/Edit/Timing/ControlPointTable.cs | 4 +-- .../Edit/Timing/LabelledTimeSignature.cs | 2 +- .../Edit/Timing/SliderWithTextBoxInput.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 +- .../Edit/Timing/WaveformComparisonDisplay.cs | 2 +- osu.Game/Screens/Edit/Verify/IssueList.cs | 2 +- osu.Game/Screens/Menu/Disclaimer.cs | 2 +- .../OnlinePlay/Components/BeatmapTitle.cs | 2 +- .../Components/MatchBeatmapDetailArea.cs | 2 +- .../Components/OnlinePlayBackgroundSprite.cs | 2 +- .../Components/OverlinedPlaylistHeader.cs | 2 +- .../OnlinePlay/Components/ParticipantsList.cs | 2 +- .../Components/StarRatingRangeDisplay.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 2 +- osu.Game/Screens/OnlinePlay/Header.cs | 4 +-- .../Lounge/Components/PlaylistCountPill.cs | 2 +- .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- .../Lounge/LoungeBackgroundScreen.cs | 2 +- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 2 +- .../Match/MultiplayerReadyButton.cs | 2 +- .../Match/MultiplayerSpectateButton.cs | 2 +- .../Playlist/MultiplayerPlaylistTabControl.cs | 2 +- .../Match/Playlist/MultiplayerQueueList.cs | 2 +- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- .../Multiplayer/MultiplayerPlayer.cs | 2 +- .../Spectate/MultiSpectatorScreen.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 2 +- .../Playlists/PlaylistsRoomSubScreen.cs | 2 +- osu.Game/Screens/Play/FailAnimation.cs | 2 +- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 4 +-- .../Screens/Play/HUD/HoldForMenuButton.cs | 2 +- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- osu.Game/Screens/Play/KeyCounterDisplay.cs | 8 +++--- osu.Game/Screens/Play/Player.cs | 4 +-- osu.Game/Screens/Play/ReplayPlayer.cs | 2 +- .../Expanded/Statistics/ComboStatistic.cs | 2 +- .../Ranking/Statistics/StatisticsPanel.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- osu.Game/Screens/Select/BeatmapDetailArea.cs | 2 +- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- .../Screens/Select/Carousel/TopLocalRank.cs | 2 +- .../Screens/Select/Details/AdvancedStats.cs | 2 +- .../Screens/Select/PlayBeatmapDetailArea.cs | 2 +- osu.Game/Skinning/DefaultSkin.cs | 2 +- osu.Game/Skinning/Editor/SkinEditor.cs | 4 +-- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 2 +- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 12 ++++----- osu.Game/Skinning/SkinImporter.cs | 2 +- osu.Game/Skinning/SkinProvidingContainer.cs | 6 ++--- osu.Game/Storyboards/StoryboardSprite.cs | 10 +++---- osu.Game/Tests/Visual/ScreenTestScene.cs | 4 +-- 182 files changed, 309 insertions(+), 309 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index f2531c1cae..d0b6953c30 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -57,7 +57,7 @@ namespace osu.Desktop client.OnReady += onReady; // safety measure for now, until we performance test / improve backoff for failed connections. - client.OnConnectionFailed += (_, __) => client.Deinitialize(); + client.OnConnectionFailed += (_, _) => client.Deinitialize(); client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network); diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 7a5809e915..712f300671 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -129,18 +129,18 @@ namespace osu.Desktop [SupportedOSPlatform("windows")] private static void setupSquirrel() { - SquirrelAwareApp.HandleEvents(onInitialInstall: (version, tools) => + SquirrelAwareApp.HandleEvents(onInitialInstall: (_, tools) => { tools.CreateShortcutForThisExe(); tools.CreateUninstallerRegistryEntry(); - }, onAppUpdate: (version, tools) => + }, onAppUpdate: (_, tools) => { tools.CreateUninstallerRegistryEntry(); - }, onAppUninstall: (version, tools) => + }, onAppUninstall: (_, tools) => { tools.RemoveShortcutForThisExe(); tools.RemoveUninstallerRegistryEntry(); - }, onEveryRun: (version, tools, firstRun) => + }, onEveryRun: (_, _, _) => { // While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently // causes the right-click context menu to function incorrectly. diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index b26fc3fd59..5ffda6504e 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -31,7 +31,7 @@ namespace osu.Game.Benchmarks realm = new RealmAccess(storage, OsuGameBase.CLIENT_DATABASE_FILENAME); - realm.Run(r => + realm.Run(_ => { realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); }); diff --git a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs index 54d26a0f3d..0de992c1df 100644 --- a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Catch.Tests new JuiceStreamPathVertex(20, -5) })); - removeCount = path.RemoveVertices((_, i) => true); + removeCount = path.RemoveVertices((_, _) => true); Assert.That(removeCount, Is.EqualTo(1)); Assert.That(path.Vertices, Is.EqualTo(new[] { diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 655edf7e08..4886942dc6 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Tests hyperDashCount = 0; // this needs to be done within the frame stable context due to how quickly hyperdash state changes occur. - Player.DrawableRuleset.FrameStableComponents.OnUpdate += d => + Player.DrawableRuleset.FrameStableComponents.OnUpdate += _ => { var catcher = Player.ChildrenOfType().FirstOrDefault(); diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 01156ab021..f31dc3ef9c 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -135,15 +135,15 @@ namespace osu.Game.Rulesets.Catch.Edit { switch (BlueprintContainer.CurrentTool) { - case SelectTool _: + case SelectTool: if (EditorBeatmap.SelectedHitObjects.Count == 0) return null; double minTime = EditorBeatmap.SelectedHitObjects.Min(hitObject => hitObject.StartTime); return getLastSnappableHitObject(minTime); - case FruitCompositionTool _: - case JuiceStreamCompositionTool _: + case FruitCompositionTool: + case JuiceStreamCompositionTool: if (!CursorInPlacementArea) return null; diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs index 4390234b59..889d3909bd 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs @@ -42,10 +42,10 @@ namespace osu.Game.Rulesets.Catch.Edit case Droplet droplet: return droplet is TinyDroplet ? PositionRange.EMPTY : new PositionRange(droplet.OriginalX); - case JuiceStream _: + case JuiceStream: return GetPositionRange(hitObject.NestedHitObjects); - case BananaShower _: + case BananaShower: // A banana shower occupies the whole screen width. return new PositionRange(0, CatchPlayfield.WIDTH); diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 319cb1bfc9..5aac521d0b 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Catch.Edit { switch (hitObject) { - case BananaShower _: + case BananaShower: return false; case JuiceStream juiceStream: diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 6a3ec336d1..efc841dfac 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -389,13 +389,13 @@ namespace osu.Game.Rulesets.Catch.UI { switch (source) { - case Fruit _: + case Fruit: return caughtFruitPool.Get(); - case Banana _: + case Banana: return caughtBananaPool.Get(); - case Droplet _: + case Droplet: return caughtDropletPool.Get(); default: diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 2b4f497785..90cd7f57b5 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps switch (original) { - case IHasDistance _: + case IHasDistance: { var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); conversion = generator; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 8002410f70..979aaa1cf5 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty { switch (m) { - case ManiaModNoFail _: - case ManiaModEasy _: - case ManiaModHalfTime _: + case ManiaModNoFail: + case ManiaModEasy: + case ManiaModHalfTime: scoreMultiplier *= 0.5; break; } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 98a492450e..4723416c30 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -146,56 +146,56 @@ namespace osu.Game.Rulesets.Mania { switch (mod) { - case ManiaModKey1 _: + case ManiaModKey1: value |= LegacyMods.Key1; break; - case ManiaModKey2 _: + case ManiaModKey2: value |= LegacyMods.Key2; break; - case ManiaModKey3 _: + case ManiaModKey3: value |= LegacyMods.Key3; break; - case ManiaModKey4 _: + case ManiaModKey4: value |= LegacyMods.Key4; break; - case ManiaModKey5 _: + case ManiaModKey5: value |= LegacyMods.Key5; break; - case ManiaModKey6 _: + case ManiaModKey6: value |= LegacyMods.Key6; break; - case ManiaModKey7 _: + case ManiaModKey7: value |= LegacyMods.Key7; break; - case ManiaModKey8 _: + case ManiaModKey8: value |= LegacyMods.Key8; break; - case ManiaModKey9 _: + case ManiaModKey9: value |= LegacyMods.Key9; break; - case ManiaModDualStages _: + case ManiaModDualStages: value |= LegacyMods.KeyCoop; break; - case ManiaModFadeIn _: + case ManiaModFadeIn: value |= LegacyMods.FadeIn; value &= ~LegacyMods.Hidden; // this is toggled on in the base call due to inheritance, but we don't want that. break; - case ManiaModMirror _: + case ManiaModMirror: value |= LegacyMods.Mirror; break; - case ManiaModRandom _: + case ManiaModRandom: value |= LegacyMods.Random; break; } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index c7c7a6003e..22347d21b8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Mods var rng = new Random((int)Seed.Value); int availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; - var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => rng.Next()).ToList(); + var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(_ => rng.Next()).ToList(); beatmap.HitObjects.OfType().ForEach(h => h.Column = shuffledColumns[h.Column]); } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 3814ad84f1..26572de412 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -57,11 +57,11 @@ namespace osu.Game.Rulesets.Mania.Replays { switch (point) { - case HitPoint _: + case HitPoint: actions.Add(columnActions[point.Column]); break; - case ReleasePoint _: + case ReleasePoint: actions.Remove(columnActions[point.Column]); break; } diff --git a/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs b/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs index 2c4310202e..7c4ab2f5f4 100644 --- a/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Tests skin.Setup(s => s.GetTexture(It.IsAny())).CallBase(); skin.Setup(s => s.GetTexture(It.IsIn(textureFilenames), It.IsAny(), It.IsAny())) - .Returns((string componentName, WrapMode _, WrapMode __) => new Texture(1, 1) { AssetName = componentName }); + .Returns((string componentName, WrapMode _, WrapMode _) => new Texture(1, 1) { AssetName = componentName }); Child = new DependencyProvidingContainer { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index fe8bba3ed8..4f6d6376bf 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods lastResult = null; spinner = nextSpinner; - spinner.OnNewResult += (o, result) => lastResult = result; + spinner.OnNewResult += (_, result) => lastResult = result; } return lastResult?.Type == HitResult.Great; @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods return false; spinner = nextSpinner; - spinner.OnNewResult += (o, result) => results.Add(result); + spinner.OnNewResult += (_, result) => results.Add(result); results.Clear(); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 066a114f66..60896b17bf 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit }); selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy(); - selectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); + selectedHitObjects.CollectionChanged += (_, _) => updateDistanceSnapGrid(); placementObject = EditorBeatmap.PlacementObject.GetBoundCopy(); placementObject.ValueChanged += _ => updateDistanceSnapGrid(); @@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Edit switch (BlueprintContainer.CurrentTool) { - case SelectTool _: + case SelectTool: if (!EditorBeatmap.SelectedHitObjects.Any()) return; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index 3d4a26b3ff..e25845f5ab 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableHitObject(DrawableHitObject drawable) { - drawable.ApplyCustomUpdateState += (drawableObject, state) => + drawable.ApplyCustomUpdateState += (drawableObject, _) => { if (!(drawableObject is DrawableHitCircle drawableHitCircle)) return; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index f9422e1ff9..11ceb0f710 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawableObject) { - case DrawableSliderTail _: + case DrawableSliderTail: using (drawableObject.BeginAbsoluteSequence(fadeStartTime)) drawableObject.FadeOut(fadeDuration); @@ -165,14 +165,14 @@ namespace osu.Game.Rulesets.Osu.Mods switch (hitObject) { - case Slider _: + case Slider: return (fadeOutStartTime, longFadeDuration); - case SliderTick _: + case SliderTick: double tickFadeOutDuration = Math.Min(hitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); return (hitObject.StartTime - tickFadeOutDuration, tickFadeOutDuration); - case Spinner _: + case Spinner: return (fadeOutStartTime + longFadeDuration, fadeOutDuration); default: diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index d96724929f..44942e9e37 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -44,13 +44,13 @@ namespace osu.Game.Rulesets.Osu.Mods // apply grow effect switch (drawable) { - case DrawableSliderHead _: - case DrawableSliderTail _: + case DrawableSliderHead: + case DrawableSliderTail: // special cases we should *not* be scaling. break; - case DrawableSlider _: - case DrawableHitCircle _: + case DrawableSlider: + case DrawableHitCircle: { using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) drawable.ScaleTo(StartScale.Value).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 51994a3e1a..5a08df3803 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -34,10 +34,10 @@ namespace osu.Game.Rulesets.Osu.Mods { switch (drawable) { - case DrawableSliderHead _: - case DrawableSliderTail _: - case DrawableSliderTick _: - case DrawableSliderRepeat _: + case DrawableSliderHead: + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: return; default: diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index dcb47347ef..a5468ff613 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Objects public Slider() { - SamplesBindable.CollectionChanged += (_, __) => UpdateNestedSamples(); + SamplesBindable.CollectionChanged += (_, _) => UpdateNestedSamples(); Path.Version.ValueChanged += _ => updateNestedPositions(); } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 8b9dc71a8b..120ce32612 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -120,19 +120,19 @@ namespace osu.Game.Rulesets.Osu { switch (mod) { - case OsuModAutopilot _: + case OsuModAutopilot: value |= LegacyMods.Autopilot; break; - case OsuModSpunOut _: + case OsuModSpunOut: value |= LegacyMods.SpunOut; break; - case OsuModTarget _: + case OsuModTarget: value |= LegacyMods.Target; break; - case OsuModTouchDevice _: + case OsuModTouchDevice: value |= LegacyMods.TouchDevice; break; } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 27029afece..b0155c02cf 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Replays hitWindows = slider.TailCircle.HitWindows; break; - case Spinner _: + case Spinner: hitWindows = defaultHitWindows; break; } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 7a71ef6c65..34a1bd40e9 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Scoring { switch (hitObject) { - case HitCircle _: + case HitCircle: return new OsuHitCircleJudgementResult(hitObject, judgement); default: diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index e4ca0d2ea8..d5cc469ca9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy break; - case DrawableSpinnerBonusTick _: + case DrawableSpinnerBonusTick: if (state == ArmedState.Hit) glow.FlashColour(Color4.White, 200); diff --git a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs index fba225d464..6330208d37 100644 --- a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.UI switch (obj) { - case DrawableSpinner _: + case DrawableSpinner: continue; case DrawableSlider slider: diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index ad27428010..3179b37d5a 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.UI // note: `Slider`'s `ProxiedLayer` is added when its nested `DrawableHitCircle` is loaded. switch (drawable) { - case DrawableSpinner _: + case DrawableSpinner: spinnerProxies.Add(drawable.CreateProxy()); break; diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index c413226e63..3a156d4d25 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -88,11 +88,11 @@ namespace osu.Game.Rulesets.Osu.Utils switch (hitObject) { - case HitCircle _: + case HitCircle: shift = clampHitCircleToPlayfield(current); break; - case Slider _: + case Slider: shift = clampSliderToPlayfield(current); break; } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index 51772df4a7..cdfab4a215 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Setup judgements", () => { judged = false; - Player.ScoreProcessor.NewJudgement += b => judged = true; + Player.ScoreProcessor.NewJudgement += _ => judged = true; }); AddUntilStep("swell judged", () => judged); AddAssert("failed", () => Player.GameplayState.HasFailed); diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 69eace4302..e065bb43fd 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -48,8 +48,8 @@ namespace osu.Game.Rulesets.Taiko.Mods { switch (hitObject) { - case DrawableDrumRollTick _: - case DrawableHit _: + case DrawableDrumRollTick: + case DrawableHit: double preempt = drawableRuleset.TimeRange.Value / drawableRuleset.ControlPointAt(hitObject.HitObject.StartTime).Multiplier; double start = hitObject.HitObject.StartTime - preempt * fade_out_start_time; double duration = preempt * fade_out_duration; diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 8bc0dc6df0..20f3304c30 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Objects DisplayColour.Value = Type == HitType.Centre ? COLOUR_CENTRE : COLOUR_RIM; }); - SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples()); + SamplesBindable.BindCollectionChanged((_, _) => updateTypeFromSamples()); } private void updateTypeFromSamples() diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs index b7bdd98d2a..d4d59d5d44 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Objects protected TaikoStrongableHitObject() { IsStrongBindable.BindValueChanged(_ => updateSamplesFromType()); - SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples()); + SamplesBindable.BindCollectionChanged((_, _) => updateTypeFromSamples()); } private void updateTypeFromSamples() diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 4e0c8029fb..4ef7c24464 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -245,7 +245,7 @@ namespace osu.Game.Rulesets.Taiko.UI barLinePlayfield.Add(barLine); break; - case DrawableTaikoHitObject _: + case DrawableTaikoHitObject: base.Add(h); break; @@ -261,7 +261,7 @@ namespace osu.Game.Rulesets.Taiko.UI case DrawableBarLine barLine: return barLinePlayfield.Remove(barLine); - case DrawableTaikoHitObject _: + case DrawableTaikoHitObject: return base.Remove(h); default: @@ -280,12 +280,12 @@ namespace osu.Game.Rulesets.Taiko.UI switch (result.Judgement) { - case TaikoStrongJudgement _: + case TaikoStrongJudgement: if (result.IsHit) hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).ParentHitObject)?.VisualiseSecondHit(result); break; - case TaikoDrumRollTickJudgement _: + case TaikoDrumRollTickJudgement: if (!result.IsHit) break; diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 7f466925a4..c31aafa67f 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Collections.IO public async Task TestImportMalformedDatabase() { bool exceptionThrown = false; - UnhandledExceptionEventHandler setException = (_, __) => exceptionThrown = true; + UnhandledExceptionEventHandler setException = (_, _) => exceptionThrown = true; using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) { diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index c28a0c63af..65f805bafb 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Database realm.RegisterCustomSubscription(r => { - var subscription = r.All().QueryAsyncWithNotifications((sender, changes, error) => + var subscription = r.All().QueryAsyncWithNotifications((_, _, _) => { realm.Run(_ => { diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 416216062e..00a667521d 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -189,7 +189,7 @@ namespace osu.Game.Tests.Database }); // Can't be used, even from within a valid context. - realm.Run(threadContext => + realm.Run(_ => { Assert.Throws(() => { diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs index efab884d37..22aa78838a 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.NonVisual.Skinning { // use an incrementing width to allow assertion matching on correct textures as they turn from uploads into actual textures. int width = 1; - Textures = fileNames.ToDictionary(fileName => fileName, fileName => new TextureUpload(new Image(width, width++))); + Textures = fileNames.ToDictionary(fileName => fileName, _ => new TextureUpload(new Image(width, width++))); } public TextureUpload Get(string name) => Textures.GetValueOrDefault(name); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index f9a3695d65..a79ba0ae5d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -322,8 +322,8 @@ namespace osu.Game.Tests.Visual.Gameplay { switch (h) { - case TestPooledHitObject _: - case TestPooledParentHitObject _: + case TestPooledHitObject: + case TestPooledParentHitObject: return null; case TestParentHitObject p: diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index 0b737f5110..ce01bf2fb5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void LoadComplete() { base.LoadComplete(); - HealthProcessor.FailConditions += (_, __) => true; + HealthProcessor.FailConditions += (_, _) => true; } private double lastFrequency = double.MaxValue; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index 90a4b536bb..5e87eff717 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void LoadComplete() { base.LoadComplete(); - HealthProcessor.FailConditions += (_, __) => true; + HealthProcessor.FailConditions += (_, _) => true; } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 3bebf2b68b..f319290441 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestToggleEditor() { - AddToggleStep("toggle editor visibility", visible => skinEditor.ToggleVisibility()); + AddToggleStep("toggle editor visibility", _ => skinEditor.ToggleVisibility()); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 757a261de6..16593effd6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Gameplay Child = new SkinProvidingContainer(secondarySource) { RelativeSizeAxes = Axes.Both, - Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation")) + Child = consumer = new SkinConsumer("test", _ => new NamedBox("Default Implementation")) } }; }); @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation")))); + AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", _ => new NamedBox("Default Implementation")))); AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1); } @@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation")))); + AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", _ => new NamedBox("Default Implementation")))); AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); AddStep("disable", () => target.Disable()); AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 68d024e63f..002e35f742 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay base.SetUpSteps(); AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true)); AddStep("set dim level to 0", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0)); - AddStep("reset fail conditions", () => currentFailConditions = (_, __) => false); + AddStep("reset fail conditions", () => currentFailConditions = (_, _) => false); AddStep("set storyboard duration to 2s", () => currentStoryboardDuration = 2000); AddStep("set ShowResults = true", () => showResults = true); } @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay { CreateTest(() => { - AddStep("fail on first judgement", () => currentFailConditions = (_, __) => true); + AddStep("fail on first judgement", () => currentFailConditions = (_, _) => true); // Fail occurs at 164ms with the provided beatmap. // Fail animation runs for 2.5s realtime but the gameplay time change is *variable* due to the frequency transform being applied, so we need a bit of lenience. diff --git a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs index 43ca47778a..631f2e707a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // To emulate `MultiplayerClient.CurrentMatchPlayingUserIds` we need a bindable list of *only IDs*. // This tracks the list of users 1:1. - MultiplayerUsers.BindCollectionChanged((c, e) => + MultiplayerUsers.BindCollectionChanged((_, e) => { switch (e.Action) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs index 5b4e0a88aa..be1f21a7b2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var mockLounge = new Mock(); mockLounge .Setup(l => l.Join(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>())) - .Callback, Action>((a, b, c, d) => + .Callback, Action>((_, _, _, d) => { Task.Run(() => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 0cdc144b6a..a800b21bc9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Multiplayer setRoomCountdown(countdownStart.Duration); break; - case StopCountdownRequest _: + case StopCountdownRequest: multiplayerRoom.Countdown = null; raiseRoomUpdated(); break; diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs index f28eaf5ad0..266e98db15 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs @@ -219,7 +219,7 @@ namespace osu.Game.Tests.Visual.Online { base.LoadComplete(); - Metadata.BindValueChanged(metadata => + Metadata.BindValueChanged(_ => { foreach (var b in this.ChildrenOfType()) b.Action = () => YearChanged?.Invoke(b.Year); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index a46e675370..8a04cd96fe 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -187,8 +187,8 @@ namespace osu.Game.Tests.Visual.Playlists // pre-check for requests we should be handling (as they are scheduled below). switch (request) { - case ShowPlaylistUserScoreRequest _: - case IndexPlaylistScoresRequest _: + case ShowPlaylistUserScoreRequest: + case IndexPlaylistScoresRequest: break; default: diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index abf65602f9..9791bb6248 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Settings public void ToggleVisibility() { AddWaitStep("wait some", 5); - AddToggleStep("toggle visibility", visible => settings.ToggleVisibility()); + AddToggleStep("toggle visibility", _ => settings.ToggleVisibility()); } [Test] diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 6490fd822e..f9d18f4236 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -91,19 +91,19 @@ namespace osu.Game.Tests.Visual.SongSelect switch (instance) { - case OsuRuleset _: + case OsuRuleset: testInfoLabels(5); break; - case TaikoRuleset _: + case TaikoRuleset: testInfoLabels(5); break; - case CatchRuleset _: + case CatchRuleset: testInfoLabels(5); break; - case ManiaRuleset _: + case ManiaRuleset: testInfoLabels(4); break; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 6cf66963a5..368babc9b5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Set time before zero", () => { - beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => + beatContainer.NewBeat = (i, timingControlPoint, _, _) => { lastActuationTime = gameplayClockContainer.CurrentTime; lastTimingPoint = timingControlPoint; @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Set time before zero", () => { - beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => + beatContainer.NewBeat = (i, timingControlPoint, _, _) => { lastBeatIndex = i; lastBpm = timingControlPoint.BPM; @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("bind event", () => { - beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => lastBpm = timingControlPoint.BPM; + beatContainer.NewBeat = (_, timingControlPoint, _, _) => lastBpm = timingControlPoint.BPM; }); AddUntilStep("wait for trigger", () => lastBpm != null); @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface actualEffectPoint = null; beatContainer.AllowMistimedEventFiring = false; - beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => + beatContainer.NewBeat = (_, _, effectControlPoint, _) => { if (Precision.AlmostEquals(gameplayClockContainer.CurrentTime + earlyActivationMilliseconds, expectedEffectPoint.Time, BeatSyncedContainer.MISTIMED_ALLOWANCE)) actualEffectPoint = effectControlPoint; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 1107fad834..44f2da2b95 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -77,13 +77,13 @@ 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().Underscore())) : "")}", true); + control.General.BindCollectionChanged((_, _) => 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); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); - control.Extra.BindCollectionChanged((u, v) => extra.Text = $"Extra: {(control.Extra.Any() ? string.Join('.', control.Extra.Select(i => i.ToString().ToLowerInvariant())) : "")}", true); - control.Ranks.BindCollectionChanged((u, v) => ranks.Text = $"Ranks: {(control.Ranks.Any() ? string.Join('.', control.Ranks.Select(i => i.ToString())) : "")}", true); + control.Extra.BindCollectionChanged((_, _) => extra.Text = $"Extra: {(control.Extra.Any() ? string.Join('.', control.Extra.Select(i => i.ToString().ToLowerInvariant())) : "")}", true); + control.Ranks.BindCollectionChanged((_, _) => ranks.Text = $"Ranks: {(control.Ranks.Any() ? string.Join('.', control.Ranks.Select(i => i.ToString())) : "")}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); control.ExplicitContent.BindValueChanged(e => explicitMap.Text = $"Explicit Maps: {e.NewValue}", true); }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index a3ae55670a..b845b85e1f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.Reset(); performer.Setup(g => g.PerformFromScreen(It.IsAny>(), It.IsAny>())) - .Callback((Action action, IEnumerable types) => action(null)); + .Callback((Action action, IEnumerable _) => action(null)); notificationOverlay.Setup(n => n.Post(It.IsAny())) .Callback((Notification n) => lastNotification = n); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 31061dc109..006707d064 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -387,7 +387,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => panel.Filtered.Value)); AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value)); - AddStep("make double time valid again", () => modSelectOverlay.IsValidMod = m => true); + AddStep("make double time valid again", () => modSelectOverlay.IsValidMod = _ => true); AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => !panel.Filtered.Value)); AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value)); } diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs index 8eccb9e0e0..76d12a6b03 100644 --- a/osu.Game.Tournament/Components/DateTextBox.cs +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Components { base.Current = new Bindable(string.Empty); - ((OsuTextBox)Control).OnCommit += (sender, newText) => + ((OsuTextBox)Control).OnCommit += (sender, _) => { try { diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs index 6e9c1120e4..348fd8cd76 100644 --- a/osu.Game.Tournament/Components/DrawableTeamFlag.cs +++ b/osu.Game.Tournament/Components/DrawableTeamFlag.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Components FillMode = FillMode.Fill }; - (flag = team.FlagName.GetBoundCopy()).BindValueChanged(acronym => flagSprite.Texture = textures.Get($@"Flags/{team.FlagName}"), true); + (flag = team.FlagName.GetBoundCopy()).BindValueChanged(_ => flagSprite.Texture = textures.Get($@"Flags/{team.FlagName}"), true); } } } diff --git a/osu.Game.Tournament/Components/DrawableTeamTitle.cs b/osu.Game.Tournament/Components/DrawableTeamTitle.cs index b7e936026c..e64e08a921 100644 --- a/osu.Game.Tournament/Components/DrawableTeamTitle.cs +++ b/osu.Game.Tournament/Components/DrawableTeamTitle.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tournament.Components { if (team == null) return; - (acronym = team.Acronym.GetBoundCopy()).BindValueChanged(acronym => Text.Text = team?.FullName.Value ?? string.Empty, true); + (acronym = team.Acronym.GetBoundCopy()).BindValueChanged(_ => Text.Text = team?.FullName.Value ?? string.Empty, true); } } } diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs index 84cae92a39..eb1dde21e7 100644 --- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs +++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tournament.Components { if (Team == null) return; - (acronym = Team.Acronym.GetBoundCopy()).BindValueChanged(acronym => AcronymText.Text = Team?.Acronym.Value?.ToUpperInvariant() ?? string.Empty, true); + (acronym = Team.Acronym.GetBoundCopy()).BindValueChanged(_ => AcronymText.Text = Team?.Acronym.Value?.ToUpperInvariant() ?? string.Empty, true); } } } diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 58ba3c1e8b..32da4d1b36 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -219,7 +219,7 @@ namespace osu.Game.Tournament.Screens.Drawings } } - writeOp = writeOp?.ContinueWith(t => { writeAction(); }) ?? Task.Run(writeAction); + writeOp = writeOp?.ContinueWith(_ => { writeAction(); }) ?? Task.Run(writeAction); } private void reloadTeams() diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index 6052bcdeb7..4261828df2 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tournament.Screens.Editors AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches")); - LadderInfo.Matches.CollectionChanged += (_, __) => updateMessage(); + LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage(); updateMessage(); } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs index 544725996a..466b9ed482 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs @@ -49,10 +49,10 @@ namespace osu.Game.Tournament.Screens.Ladder.Components }; name = round.Name.GetBoundCopy(); - name.BindValueChanged(n => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpperInvariant(), true); + name.BindValueChanged(_ => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpperInvariant(), true); description = round.Description.GetBoundCopy(); - description.BindValueChanged(n => textDescription.Text = round.Description.Value?.ToUpperInvariant(), true); + description.BindValueChanged(_ => textDescription.Text = round.Description.Value?.ToUpperInvariant(), true); } } } diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index f274503894..23bfa84afc 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tournament.Screens.Ladder foreach (var match in LadderInfo.Matches) addMatch(match); - LadderInfo.Rounds.CollectionChanged += (_, __) => layout.Invalidate(); + LadderInfo.Rounds.CollectionChanged += (_, _) => layout.Invalidate(); LadderInfo.Matches.CollectionChanged += (_, args) => { switch (args.Action) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index c2c6c271cb..537fbfc038 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -137,7 +137,7 @@ namespace osu.Game.Tournament heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0; }), true); - windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => + windowMode.BindValueChanged(_ => ScheduleAfterChildren(() => { windowMode.Value = WindowMode.Windowed; }), true); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 95d2adc4fa..ccbe77fa95 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -267,7 +267,7 @@ namespace osu.Game.Tournament } else { - req.Success += res => { populate(); }; + req.Success += _ => { populate(); }; req.Failure += _ => { user.OnlineID = 1; diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index afbfc2d368..296b259d72 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -204,12 +204,12 @@ namespace osu.Game.Tournament switch (currentScreen) { - case MapPoolScreen _: + case MapPoolScreen: chatContainer.FadeIn(TournamentScreen.FADE_DELAY); chatContainer.ResizeWidthTo(1, 500, Easing.OutQuint); break; - case GameplayScreen _: + case GameplayScreen: chatContainer.FadeIn(TournamentScreen.FADE_DELAY); chatContainer.ResizeWidthTo(0.5f, 500, Easing.OutQuint); break; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index eda7ef0bcc..4be6b5eede 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -254,12 +254,12 @@ namespace osu.Game.Beatmaps.ControlPoints switch (newPoint) { - case TimingControlPoint _: + case TimingControlPoint: // Timing points are a special case and need to be added regardless of fallback availability. existing = BinarySearch(TimingPoints, time); break; - case EffectControlPoint _: + case EffectControlPoint: existing = EffectPointAt(time); break; } diff --git a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs index 9b97df906b..80af4108c7 100644 --- a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs @@ -102,7 +102,7 @@ namespace osu.Game.Beatmaps.Drawables // Matches osu-stable, in order to provide new users with roughly the same randomised selection of bundled beatmaps. var random = new LegacyRandom(DateTime.UtcNow.Year * 1000 + (DateTime.UtcNow.DayOfYear / 7)); - downloadableFilenames.AddRange(sourceFilenames.OrderBy(x => random.NextDouble()).Take(limit ?? int.MaxValue)); + downloadableFilenames.AddRange(sourceFilenames.OrderBy(_ => random.NextDouble()).Take(limit ?? int.MaxValue)); } catch { } } diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs index 2f11c18993..4f292a9a1f 100644 --- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps.Formats { public static void Register() { - AddDecoder("{", m => new JsonBeatmapDecoder()); + AddDecoder("{", _ => new JsonBeatmapDecoder()); } protected override void ParseStreamInto(LineBufferedReader stream, Beatmap output) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 984e88d3a9..03c63ff4f2 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -373,11 +373,11 @@ namespace osu.Game.Beatmaps.Formats switch (hitObject) { - case IHasPath _: + case IHasPath: type |= LegacyHitObjectType.Slider; break; - case IHasDuration _: + case IHasDuration: if (onlineRulesetID == 3) type |= LegacyHitObjectType.Hold; else diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 3021688aaf..b8f60f0bc6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.Formats { // note that this isn't completely correct AddDecoder(@"osu file format v", m => new LegacyStoryboardDecoder(Parsing.ParseInt(m.Split('v').Last()))); - AddDecoder(@"[Events]", m => new LegacyStoryboardDecoder()); + AddDecoder(@"[Events]", _ => new LegacyStoryboardDecoder()); SetFallbackDecoder(() => new LegacyStoryboardDecoder()); } diff --git a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs index 19432f0c58..5c3c72c9e4 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs @@ -56,12 +56,12 @@ namespace osu.Game.Beatmaps.Legacy { switch (newPoint) { - case SampleControlPoint _: + case SampleControlPoint: // intentionally don't use SamplePointAt (we always need to consider the first sample point). var existing = BinarySearch(SamplePoints, time); return newPoint.IsRedundant(existing); - case DifficultyControlPoint _: + case DifficultyControlPoint: return newPoint.IsRedundant(DifficultyPointAt(time)); default: diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs index 23b2ef60dd..742d757bec 100644 --- a/osu.Game/Collections/BeatmapCollection.cs +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -36,7 +36,7 @@ namespace osu.Game.Collections public BeatmapCollection() { - BeatmapHashes.CollectionChanged += (_, __) => onChange(); + BeatmapHashes.CollectionChanged += (_, _) => onChange(); Name.ValueChanged += _ => onChange(); } diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index 46d90b930c..d099eb6e1b 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -67,7 +67,7 @@ namespace osu.Game.Collections // An extra bindable is enough to subvert this behaviour. base.Current = Current; - collections.BindCollectionChanged((_, __) => collectionsChanged(), true); + collections.BindCollectionChanged((_, _) => collectionsChanged(), true); Current.BindValueChanged(filterChanged, true); } @@ -233,7 +233,7 @@ namespace osu.Game.Collections if (collectionBeatmaps != null) { - collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged(); + collectionBeatmaps.CollectionChanged += (_, _) => collectionChanged(); beatmap.BindValueChanged(_ => collectionChanged(), true); } diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index c6e52a1b75..796b3c426c 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -261,7 +261,7 @@ namespace osu.Game.Collections private void backgroundSave() { int current = Interlocked.Increment(ref lastSave); - Task.Delay(100).ContinueWith(task => + Task.Delay(100).ContinueWith(_ => { if (current != lastSave) return; diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 68f9dcb541..3ea7a14826 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -454,7 +454,7 @@ namespace osu.Game.Database public IDisposable SubscribeToPropertyChanged(Func modelAccessor, Expression> propertyLookup, Action onChanged) where TModel : RealmObjectBase { - return RegisterCustomSubscription(r => + return RegisterCustomSubscription(_ => { string propertyName = getMemberName(propertyLookup); diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 25416c7fa2..a771aa04df 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -20,7 +20,7 @@ namespace osu.Game.Database { private static readonly IMapper write_mapper = new MapperConfiguration(c => { - c.ShouldMapField = fi => false; + c.ShouldMapField = _ => false; c.ShouldMapProperty = pi => pi.SetMethod?.IsPublic == true; c.CreateMap() @@ -70,7 +70,7 @@ namespace osu.Game.Database } }); - c.Internal().ForAllMaps((typeMap, expression) => + c.Internal().ForAllMaps((_, expression) => { expression.ForAllMembers(m => { @@ -87,7 +87,7 @@ namespace osu.Game.Database c.CreateMap() .ConstructUsing(_ => new BeatmapSetInfo(null)) .MaxDepth(2) - .AfterMap((s, d) => + .AfterMap((_, d) => { foreach (var beatmap in d.Beatmaps) beatmap.BeatmapSet = d; @@ -97,7 +97,7 @@ namespace osu.Game.Database // Only hasn't been done yet as we detach at the point of BeatmapInfo less often. c.CreateMap() .MaxDepth(2) - .AfterMap((s, d) => + .AfterMap((_, d) => { for (int i = 0; i < d.BeatmapSet?.Beatmaps.Count; i++) { @@ -121,7 +121,7 @@ namespace osu.Game.Database .ConstructUsing(_ => new BeatmapSetInfo(null)) .MaxDepth(2) .ForMember(b => b.Files, cc => cc.Ignore()) - .AfterMap((s, d) => + .AfterMap((_, d) => { foreach (var beatmap in d.Beatmaps) beatmap.BeatmapSet = d; @@ -135,14 +135,14 @@ namespace osu.Game.Database private static void applyCommonConfiguration(IMapperConfigurationExpression c) { - c.ShouldMapField = fi => false; + c.ShouldMapField = _ => false; // This is specifically to avoid mapping explicit interface implementations. // If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>. // Takes a bit of effort to determine whether this is the case though, see https://stackoverflow.com/questions/951536/how-do-i-tell-whether-a-type-implements-ilist c.ShouldMapProperty = pi => pi.GetMethod?.IsPublic == true; - c.Internal().ForAllMaps((typeMap, expression) => + c.Internal().ForAllMaps((_, expression) => { expression.ForAllMembers(m => { diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs index 5165ae59b5..b4a0c02e35 100644 --- a/osu.Game/Extensions/TaskExtensions.cs +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -31,7 +31,7 @@ namespace osu.Game.Extensions { var tcs = new TaskCompletionSource(); - task.ContinueWith(t => + task.ContinueWith(_ => { // the previous task has finished execution or been cancelled, so we can run the provided continuation. diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index aa156efee9..a2370a76cb 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Containers.Markdown { switch (markdownObject) { - case YamlFrontMatterBlock _: + case YamlFrontMatterBlock: // Don't parse YAML Frontmatter break; diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 00ed6c1c92..f55875ac58 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -74,7 +74,7 @@ namespace osu.Game.Graphics.UserInterface } //I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards - RemoveRange(Children.Where((bar, index) => index >= value.Count()).ToList()); + RemoveRange(Children.Where((_, index) => index >= value.Count()).ToList()); } } } diff --git a/osu.Game/Graphics/UserInterface/LoadingLayer.cs b/osu.Game/Graphics/UserInterface/LoadingLayer.cs index 407823fc2a..b3655eaab4 100644 --- a/osu.Game/Graphics/UserInterface/LoadingLayer.cs +++ b/osu.Game/Graphics/UserInterface/LoadingLayer.cs @@ -55,12 +55,12 @@ namespace osu.Game.Graphics.UserInterface switch (e) { // blocking scroll can cause weird behaviour when this layer is used within a ScrollContainer. - case ScrollEvent _: + case ScrollEvent: return false; // blocking touch events causes the ISourcedFromTouch versions to not be fired, potentially impeding behaviour of drawables *above* the loading layer that may utilise these. // note that this will not work well if touch handling elements are beneath this loading layer (something to consider for the future). - case TouchEvent _: + case TouchEvent: return false; } diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index a8051a94d9..d9274bf2cb 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -68,7 +68,7 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(star_spacing), - ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(i => CreateStar()) + ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(_ => CreateStar()) } }; } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 974abc4036..14a041b459 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Input.Bindings protected override void LoadComplete() { - realmSubscription = realm.RegisterForNotifications(queryRealmKeyBindings, (sender, changes, error) => + realmSubscription = realm.RegisterForNotifications(queryRealmKeyBindings, (sender, _, _) => { // The first fire of this is a bit redundant as this is being called in base.LoadComplete, // but this is safest in case the subscription is restored after a context recycle. diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index 279cfb5a4e..45036b3e41 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -80,11 +80,11 @@ namespace osu.Game.Input switch (e) { - case KeyDownEvent _: - case KeyUpEvent _: - case MouseDownEvent _: - case MouseUpEvent _: - case MouseMoveEvent _: + case KeyDownEvent: + case KeyUpEvent: + case MouseDownEvent: + case MouseUpEvent: + case MouseMoveEvent: return updateLastInteractionTime(); default: diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 1e3add6201..19708cc07d 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -40,7 +40,7 @@ namespace osu.Game.Online // Used to interact with manager classes that don't support interface types. Will eventually be replaced. var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; - realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, _, _) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 4c762d002d..48dfaadfa5 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -465,7 +465,7 @@ namespace osu.Game.Online.Chat default: var req = new JoinChannelRequest(channel); req.Success += () => joinChannel(channel, fetchInitialMessages); - req.Failure += ex => LeaveChannel(channel); + req.Failure += _ => LeaveChannel(channel); api.Queue(req); return channel; } diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index f9000c01ef..61e9eaa8c0 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -64,7 +64,7 @@ namespace osu.Game.Online this.preferMessagePack = preferMessagePack; apiState.BindTo(api.State); - apiState.BindValueChanged(state => connectIfPossible(), true); + apiState.BindValueChanged(_ => connectIfPossible(), true); } public void Reconnect() diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 032215f5d3..2fd8445980 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -101,7 +101,7 @@ namespace osu.Game.Online.Rooms // handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow). realmSubscription?.Dispose(); - realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(), (items, changes, ___) => + realmSubscription = realm.RegisterForNotifications(_ => filteredBeatmaps(), (_, changes, _) => { if (changes == null) return; diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index cab6bd0722..680bb16264 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -48,7 +48,7 @@ namespace osu.Game.Online realmSubscription = realm.RegisterForNotifications(r => r.All().Where(s => ((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) || (!string.IsNullOrEmpty(s.Hash) && s.Hash == TrackedItem.Hash)) - && !s.DeletePending), (items, changes, ___) => + && !s.DeletePending), (items, _, _) => { if (items.Any()) Schedule(() => UpdateState(DownloadState.LocallyAvailable)); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 1d9e2042ec..b0ba62a6ac 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -481,7 +481,7 @@ namespace osu.Game { switch (handler) { - case MidiHandler _: + case MidiHandler: return new InputSection.HandlerSection(handler); // return null for handlers that shouldn't have settings. diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 4457705676..767b8646e3 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -161,13 +161,13 @@ namespace osu.Game.Overlays.BeatmapListing queueUpdateSearch(true); }); - searchControl.General.CollectionChanged += (_, __) => queueUpdateSearch(); + searchControl.General.CollectionChanged += (_, _) => queueUpdateSearch(); searchControl.Ruleset.BindValueChanged(_ => queueUpdateSearch()); searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Extra.CollectionChanged += (_, __) => queueUpdateSearch(); - searchControl.Ranks.CollectionChanged += (_, __) => queueUpdateSearch(); + searchControl.Extra.CollectionChanged += (_, _) => queueUpdateSearch(); + searchControl.Ranks.CollectionChanged += (_, _) => queueUpdateSearch(); searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); searchControl.ExplicitContent.BindValueChanged(_ => queueUpdateSearch()); diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 1a57a89957..b27d9b3f1e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -156,7 +156,7 @@ namespace osu.Game.Overlays.BeatmapSet { base.LoadComplete(); - ruleset.ValueChanged += r => updateDisplay(); + ruleset.ValueChanged += _ => updateDisplay(); // done here so everything can bind in intialization and get the first trigger Beatmap.TriggerChange(); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 2ea94bb412..c2e54d0d7b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -217,7 +217,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores scope.BindValueChanged(_ => getScores()); ruleset.BindValueChanged(_ => getScores()); - modSelector.SelectedMods.CollectionChanged += (_, __) => getScores(); + modSelector.SelectedMods.CollectionChanged += (_, _) => getScores(); Beatmap.BindValueChanged(onBeatmapChanged); user.BindValueChanged(onUserChanged, true); diff --git a/osu.Game/Overlays/Comments/CommentEditor.cs b/osu.Game/Overlays/Comments/CommentEditor.cs index 2e8080ba43..e2a7e78356 100644 --- a/osu.Game/Overlays/Comments/CommentEditor.cs +++ b/osu.Game/Overlays/Comments/CommentEditor.cs @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Comments } }); - textBox.OnCommit += (u, v) => + textBox.OnCommit += (_, _) => { if (commitButton.IsBlocked.Value) return; diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 4ab6f4f88d..4a836e0e62 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -251,8 +251,8 @@ namespace osu.Game.Overlays.Comments if (bundle.HasMore) { int loadedTopLevelComments = 0; - pinnedContent.Children.OfType().ForEach(p => loadedTopLevelComments++); - content.Children.OfType().ForEach(p => loadedTopLevelComments++); + pinnedContent.Children.OfType().ForEach(_ => loadedTopLevelComments++); + content.Children.OfType().ForEach(_ => loadedTopLevelComments++); moreButton.Current.Value = bundle.TopLevelCount - loadedTopLevelComments; moreButton.IsLoading = false; diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs index bfeaf50b53..bfd356193d 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.Dashboard.Friends controlBackground.Colour = colourProvider.Background5; apiFriends.BindTo(api.Friends); - apiFriends.BindCollectionChanged((_, __) => Schedule(() => Users = apiFriends.ToList()), true); + apiFriends.BindCollectionChanged((_, _) => Schedule(() => Users = apiFriends.ToList()), true); } protected override void LoadComplete() diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index 08522b2921..f545e2892f 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -109,7 +109,7 @@ namespace osu.Game.Overlays.Login } }; - password.OnCommit += (sender, newText) => performLogin(); + password.OnCommit += (_, _) => performLogin(); if (api?.LastLoginError?.Message is string error) errorText.AddErrors(new[] { error }); diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 53e8452dd1..1c007c913e 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.MedalSplash s.Font = s.Font.With(size: 16); }); - medalContainer.OnLoadComplete += d => + medalContainer.OnLoadComplete += _ => { unlocked.Position = new Vector2(0f, medalContainer.DrawSize.Y / 2 + 10); infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 811db393d6..69cb3a49fc 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Mods /// public Bindable>> AvailableMods { get; } = new Bindable>>(new Dictionary>()); - private Func isValidMod = m => true; + private Func isValidMod = _ => true; /// /// A function determining whether each mod in the column should be displayed. @@ -689,11 +689,11 @@ namespace osu.Game.Overlays.Mods switch (e) { - case ClickEvent _: + case ClickEvent: OnClicked?.Invoke(); return true; - case MouseEvent _: + case MouseEvent: return true; } diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 3c25fc0421..e33fc8064f 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Music }, }; - filter.Search.OnCommit += (sender, newText) => + filter.Search.OnCommit += (_, _) => { list.FirstVisibleSet?.PerformRead(set => { diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index d76df5b1ed..719e77db83 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -129,7 +129,7 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Completed: loadingSpinner.Hide(); NotificationContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); - this.FadeOut(200).Finally(d => Completed()); + this.FadeOut(200).Finally(_ => Completed()); break; } } diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs index c66811a3e9..3bed5e7e4c 100644 --- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.OSD if (val) selectedOption = 0; break; - case Enum _: + case Enum: var values = Enum.GetValues(description.RawValue.GetType()); optionCount = values.Length; selectedOption = Convert.ToInt32(description.RawValue); diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 2c8e809379..28642f12a1 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -160,13 +160,13 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSettings.ForEach(s => bindPreviewEvent(s.Current)); - windowModeDropdown.Current.BindValueChanged(mode => + windowModeDropdown.Current.BindValueChanged(_ => { updateDisplayModeDropdowns(); updateScreenModeWarning(); }, true); - windowModes.BindCollectionChanged((sender, args) => + windowModes.BindCollectionChanged((_, _) => { if (windowModes.Count > 1) windowModeDropdown.Show(); @@ -190,7 +190,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics updateDisplayModeDropdowns(); }), true); - scalingMode.BindValueChanged(mode => + scalingMode.BindValueChanged(_ => { scalingSettings.ClearTransforms(); scalingSettings.AutoSizeDuration = transition_duration; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs index 8c8c2c96ff..453dbd2e18 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/BeatmapSettings.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importBeatmapsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(_ => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } }); } @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => { deleteBeatmapsButton.Enabled.Value = false; - Task.Run(() => beatmaps.Delete()).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); + Task.Run(() => beatmaps.Delete()).ContinueWith(_ => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); })); } }); @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() => { deleteBeatmapVideosButton.Enabled.Value = false; - Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); + Task.Run(beatmaps.DeleteAllVideos).ContinueWith(_ => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true)); })); } }); @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { restoreButton.Enabled.Value = false; - Task.Run(beatmaps.RestoreAll).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); + Task.Run(beatmaps.RestoreAll).ContinueWith(_ => Schedule(() => restoreButton.Enabled.Value = true)); } }, undeleteButton = new SettingsButton @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { undeleteButton.Enabled.Value = false; - Task.Run(beatmaps.UndeleteAll).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + Task.Run(beatmaps.UndeleteAll).ContinueWith(_ => Schedule(() => undeleteButton.Enabled.Value = true)); } } }); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 74e9ebc084..5367f644ca 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importCollectionsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Collections).ContinueWith(_ => Schedule(() => importCollectionsButton.Enabled.Value = true)); } }); } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs index 9c1281f371..70e11d45dc 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/ScoreSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importScoresButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Scores).ContinueWith(_ => Schedule(() => importScoresButton.Enabled.Value = true)); } }); } @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => { deleteScoresButton.Enabled.Value = false; - Task.Run(() => scores.Delete()).ContinueWith(t => Schedule(() => deleteScoresButton.Enabled.Value = true)); + Task.Run(() => scores.Delete()).ContinueWith(_ => Schedule(() => deleteScoresButton.Enabled.Value = true)); })); } }); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs index 0fff9fef26..c95b1d4dd8 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/SkinSettings.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { importSkinsButton.Enabled.Value = false; - legacyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); + legacyImportManager.ImportFromStableAsync(StableContent.Skins).ContinueWith(_ => Schedule(() => importSkinsButton.Enabled.Value = true)); } }); } @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new MassDeleteConfirmationDialog(() => { deleteSkinsButton.Enabled.Value = false; - Task.Run(() => skins.Delete()).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true)); + Task.Run(() => skins.Delete()).ContinueWith(_ => Schedule(() => deleteSkinsButton.Enabled.Value = true)); })); } }); diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 6494b59b09..741b6b5815 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -82,12 +82,12 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Current = dropdownBindable; - realmSubscription = realm.RegisterForNotifications(r => realm.Realm.All() + realmSubscription = realm.RegisterForNotifications(_ => realm.Realm.All() .Where(s => !s.DeletePending) .OrderByDescending(s => s.Protected) // protected skins should be at the top. .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase), skinsChanged); - configBindable.BindValueChanged(id => Scheduler.AddOnce(updateSelectedSkinFromConfig)); + configBindable.BindValueChanged(_ => Scheduler.AddOnce(updateSelectedSkinFromConfig)); dropdownBindable.BindValueChanged(dropdownSelectionChanged); } diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 6c56f3a912..5a03d66b84 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Configuration databasedSettings.Add(setting); } - bindable.ValueChanged += b => + bindable.ValueChanged += _ => { lock (pendingWrites) pendingWrites.Add(lookup); diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 3e93e576ba..c8196b6865 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -129,10 +129,10 @@ namespace osu.Game.Rulesets.Edit switch (e) { - case ScrollEvent _: + case ScrollEvent: return false; - case DoubleClickEvent _: + case DoubleClickEvent: return false; case MouseButtonEvent mouse: diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 9307e36b99..a42d4a049a 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -55,8 +55,8 @@ namespace osu.Game.Rulesets.Mods { base.LoadComplete(); - Current.BindValueChanged(current => updateCurrentFromSlider()); - beatmap.BindValueChanged(b => updateCurrentFromSlider(), true); + Current.BindValueChanged(_ => updateCurrentFromSlider()); + beatmap.BindValueChanged(_ => updateCurrentFromSlider(), true); sliderDisplayCurrent.BindValueChanged(number => { diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 2f5707562e..aea6e12a07 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToDrawableHitObject(DrawableHitObject drawable) { - drawable.OnNewResult += (o, result) => + drawable.OnNewResult += (_, result) => { if (ratesForRewinding.ContainsKey(result.HitObject)) return; if (!shouldProcessResult(result)) return; @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Mods updateTargetRate(); }; - drawable.OnRevertResult += (o, result) => + drawable.OnRevertResult += (_, result) => { if (!ratesForRewinding.ContainsKey(result.HitObject)) return; if (!shouldProcessResult(result)) return; diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index a1aad3a6b9..daa4b7c797 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mods protected ModTimeRamp() { // for preview purpose at song select. eventually we'll want to be able to update every frame. - FinalRate.BindValueChanged(val => applyRateAdjustment(double.PositiveInfinity), true); + FinalRate.BindValueChanged(_ => applyRateAdjustment(double.PositiveInfinity), true); AdjustPitch.BindValueChanged(applyPitchAdjustment); } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 8bde71dcd7..e098472999 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -126,55 +126,55 @@ namespace osu.Game.Rulesets { switch (mod) { - case ModNoFail _: + case ModNoFail: value |= LegacyMods.NoFail; break; - case ModEasy _: + case ModEasy: value |= LegacyMods.Easy; break; - case ModHidden _: + case ModHidden: value |= LegacyMods.Hidden; break; - case ModHardRock _: + case ModHardRock: value |= LegacyMods.HardRock; break; - case ModPerfect _: + case ModPerfect: value |= LegacyMods.Perfect; break; - case ModSuddenDeath _: + case ModSuddenDeath: value |= LegacyMods.SuddenDeath; break; - case ModNightcore _: + case ModNightcore: value |= LegacyMods.Nightcore; break; - case ModDoubleTime _: + case ModDoubleTime: value |= LegacyMods.DoubleTime; break; - case ModRelax _: + case ModRelax: value |= LegacyMods.Relax; break; - case ModHalfTime _: + case ModHalfTime: value |= LegacyMods.HalfTime; break; - case ModFlashlight _: + case ModFlashlight: value |= LegacyMods.Flashlight; break; - case ModCinema _: + case ModCinema: value |= LegacyMods.Cinema; break; - case ModAutoplay _: + case ModAutoplay: value |= LegacyMods.Autoplay; break; } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index f1ed23bb21..7c37913576 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.UI { switch (e) { - case MouseDownEvent _: + case MouseDownEvent: if (mouseDisabled.Value) return true; // importantly, block upwards propagation so global bindings also don't fire. diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs index 82a9db956a..55302833c1 100644 --- a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons { base.LoadComplete(); - Button.Bindable.BindValueChanged(selected => updateSelectionState(), true); + Button.Bindable.BindValueChanged(_ => updateSelectionState(), true); Action = onAction; } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index ee82ded39d..54ef5a2bd7 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts controlPointGroups.UnbindAll(); controlPointGroups.BindTo(beatmap.ControlPointInfo.Groups); - controlPointGroups.BindCollectionChanged((sender, args) => + controlPointGroups.BindCollectionChanged((_, args) => { switch (args.Action) { diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index cccb646ba4..e058cae191 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts // Run in constructor so IsRedundant calls can work correctly. controlPoints.BindTo(Group.ControlPoints); - controlPoints.BindCollectionChanged((_, __) => + controlPoints.BindCollectionChanged((_, _) => { ClearInternal(); @@ -40,15 +40,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { switch (point) { - case TimingControlPoint _: + case TimingControlPoint: AddInternal(new ControlPointVisualisation(point) { Y = 0, }); break; - case DifficultyControlPoint _: + case DifficultyControlPoint: AddInternal(new ControlPointVisualisation(point) { Y = 0.25f, }); break; - case SampleControlPoint _: + case SampleControlPoint: AddInternal(new ControlPointVisualisation(point) { Y = 0.5f, }); break; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index c1ae2ff8d1..54914f4b23 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { AddInternal(this.content = content ?? new Container { RelativeSizeAxes = Axes.Both }); - beatmap.ValueChanged += b => + beatmap.ValueChanged += _ => { updateRelativeChildSize(); }; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 6a94fce78a..40403e08ad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -293,7 +293,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); BeatDivisor.BindValueChanged(_ => updateState(), true); - divisorTextBox.OnCommit += (_, __) => setPresets(); + divisorTextBox.OnCommit += (_, _) => setPresets(); Schedule(() => GetContainingInputManager().ChangeFocus(divisorTextBox)); } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index d38aac1173..540fbf9a72 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - SelectedItems.CollectionChanged += (selectedObjects, args) => + SelectedItems.CollectionChanged += (_, args) => { switch (args.Action) { diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 1c9faadda1..0bdfc5b0a0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // bring in updates from selection changes EditorBeatmap.HitObjectUpdated += _ => Scheduler.AddOnce(UpdateTernaryStates); - SelectedItems.CollectionChanged += (sender, args) => Scheduler.AddOnce(UpdateTernaryStates); + SelectedItems.CollectionChanged += (_, _) => Scheduler.AddOnce(UpdateTernaryStates); } protected override void DeleteItems(IEnumerable items) => EditorBeatmap.RemoveRange(items); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 91af3fde16..8419d3b380 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { InternalChild = SelectionBox = CreateSelectionBox(); - SelectedItems.CollectionChanged += (sender, args) => + SelectedItems.CollectionChanged += (_, _) => { Scheduler.AddOnce(updateVisibility); }; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 08fc8383c3..648ffd9609 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -38,8 +38,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { - volume.BindValueChanged(volume => updateText()); - bank.BindValueChanged(bank => updateText(), true); + volume.BindValueChanged(_ => updateText()); + bank.BindValueChanged(_ => updateText(), true); } protected override bool OnClick(ClickEvent e) @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); // on commit, ensure that the value is correct by sourcing it from the objects' control points again. // this ensures that committing empty text causes a revert to the previous value. - bank.OnCommit += (_, __) => bank.Current.Value = getCommonBank(relevantControlPoints); + bank.OnCommit += (_, _) => bank.Current.Value = getCommonBank(relevantControlPoints); volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 544c3f8369..cfc71256e8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline controlPointGroups.UnbindAll(); controlPointGroups.BindTo(beatmap.ControlPointInfo.Groups); - controlPointGroups.BindCollectionChanged((sender, args) => + controlPointGroups.BindCollectionChanged((_, args) => { switch (args.Action) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index 4017a745e1..10355045be 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline base.LoadComplete(); controlPoints.BindTo(Group.ControlPoints); - controlPoints.BindCollectionChanged((_, __) => + controlPoints.BindCollectionChanged((_, _) => { ClearInternal(); diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index ef670c9fcf..3d18b00e75 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Edit.Compose if (composer == null) return; - EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => updateClipboardActionAvailability()); + EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, _) => updateClipboardActionAvailability()); clipboard.BindValueChanged(_ => updateClipboardActionAvailability()); composer.OnLoadComplete += _ => updateClipboardActionAvailability(); updateClipboardActionAvailability(); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 582c8f5272..0a6f9974b0 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -105,8 +105,8 @@ namespace osu.Game.Screens.Edit { switch (controlPoint) { - case DifficultyControlPoint _: - case SampleControlPoint _: + case DifficultyControlPoint: + case SampleControlPoint: // skip legacy types. continue; diff --git a/osu.Game/Screens/Edit/EditorBeatmapSkin.cs b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs index 244fd5f1e9..475c894b30 100644 --- a/osu.Game/Screens/Edit/EditorBeatmapSkin.cs +++ b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit ComboColours = new BindableList(); if (Skin.Configuration.ComboColours != null) ComboColours.AddRange(Skin.Configuration.ComboColours.Select(c => (Colour4)c)); - ComboColours.BindCollectionChanged((_, __) => updateColours()); + ComboColours.BindCollectionChanged((_, _) => updateColours()); } private void invokeSkinChanged() => BeatmapSkinChanged?.Invoke(); diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 12a6843067..40bbfeaf7d 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Setup EnableCountdown.Current.BindValueChanged(_ => updateBeatmap()); CountdownSpeed.Current.BindValueChanged(_ => updateBeatmap()); - CountdownOffset.OnCommit += (_, __) => onOffsetCommitted(); + CountdownOffset.OnCommit += (_, _) => onOffsetCommitted(); widescreenSupport.Current.BindValueChanged(_ => updateBeatmap()); epilepsyWarning.Current.BindValueChanged(_ => updateBeatmap()); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index b5c162ab11..b51da2c53d 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(group => + selectedGroup.BindValueChanged(_ => { // TODO: This should scroll the selected row into view. updateSelectedGroup(); @@ -153,7 +153,7 @@ namespace osu.Game.Screens.Edit.Timing protected override void LoadComplete() { base.LoadComplete(); - controlPoints.CollectionChanged += (_, __) => createChildren(); + controlPoints.CollectionChanged += (_, _) => createChildren(); } private void createChildren() diff --git a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs index d05777e793..998e49a6ab 100644 --- a/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs +++ b/osu.Game/Screens/Edit/Timing/LabelledTimeSignature.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit.Timing base.LoadComplete(); Current.BindValueChanged(_ => updateFromCurrent(), true); - numeratorBox.OnCommit += (_, __) => updateFromNumeratorBox(); + numeratorBox.OnCommit += (_, _) => updateFromNumeratorBox(); } private void updateFromCurrent() diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index 6a804c7e3e..4dcb5ad2ba 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Edit.Timing Current.TriggerChange(); }; - Current.BindValueChanged(val => + Current.BindValueChanged(_ => { decimal decimalValue = slider.Current.Value.ToDecimal(NumberFormatInfo.InvariantInfo); textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}"); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index c33dcc6e91..fd218209d4 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Edit.Timing }, true); controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups); - controlPointGroups.BindCollectionChanged((sender, args) => + controlPointGroups.BindCollectionChanged((_, _) => { table.ControlGroups = controlPointGroups; changeHandler?.SaveState(); diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 8413409d84..9b86969db1 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Timing { Label = "BPM"; - OnCommit += (val, isNew) => + OnCommit += (_, isNew) => { if (!isNew) return; diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 1cdcfdf167..2956a28547 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(_ => updateTimingGroup(), true); controlPointGroups.BindTo(editorBeatmap.ControlPointInfo.Groups); - controlPointGroups.BindCollectionChanged((_, __) => updateTimingGroup()); + controlPointGroups.BindCollectionChanged((_, _) => updateTimingGroup()); beatLength.BindValueChanged(_ => regenerateDisplay(true), true); diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 7ce70b4b5f..bffda4ec41 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Edit.Verify base.LoadComplete(); verify.InterpretedDifficulty.BindValueChanged(_ => refresh()); - verify.HiddenIssueTypes.BindCollectionChanged((_, __) => refresh()); + verify.HiddenIssueTypes.BindCollectionChanged((_, _) => refresh()); refresh(); } diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index f77123af52..a81658a4b6 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Menu .Then(5500) .FadeOut(250) .ScaleTo(0.9f, 250, Easing.InQuint) - .Finally(d => + .Finally(_ => { if (nextScreen != null) this.Push(nextScreen); diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs index 1dc05def0f..39a887f820 100644 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Components [BackgroundDependencyLoader] private void load() { - Playlist.CollectionChanged += (_, __) => updateText(); + Playlist.CollectionChanged += (_, _) => updateText(); updateText(); } diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index bed55e8a79..9a48769405 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay.Components switch (tab) { - case BeatmapDetailAreaPlaylistTabItem _: + case BeatmapDetailAreaPlaylistTabItem: playlistArea.Show(); break; diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs index 795f776496..fe89eaf591 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.OnlinePlay.Components InternalChild = sprite = CreateBackgroundSprite(); CurrentPlaylistItem.BindValueChanged(_ => updateBeatmap()); - Playlist.CollectionChanged += (_, __) => updateBeatmap(); + Playlist.CollectionChanged += (_, _) => updateBeatmap(); updateBeatmap(); } diff --git a/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs b/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs index 43ad5e97ec..a268d4d917 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - Playlist.BindCollectionChanged((_, __) => Details.Value = Playlist.GetTotalDuration(), true); + Playlist.BindCollectionChanged((_, _) => Details.Value = Playlist.GetTotalDuration(), true); } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs index d8515d2bd2..2f38321e13 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Components [BackgroundDependencyLoader] private void load() { - RecentParticipants.CollectionChanged += (_, __) => updateParticipants(); + RecentParticipants.CollectionChanged += (_, _) => updateParticipants(); updateParticipants(); } diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index 4cf785ce60..b6ef080e44 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Components base.LoadComplete(); DifficultyRange.BindValueChanged(_ => updateRange()); - Playlist.BindCollectionChanged((_, __) => updateRange(), true); + Playlist.BindCollectionChanged((_, _) => updateRange(), true); } private void updateRange() diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 489bf128e8..5193fe5cbf 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -181,7 +181,7 @@ namespace osu.Game.Screens.OnlinePlay // schedules added as the properties may change value while the drawable items haven't been created yet. SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(scrollToSelection)); - Items.BindCollectionChanged((_, __) => Scheduler.AddOnce(scrollToSelection), true); + Items.BindCollectionChanged((_, _) => Scheduler.AddOnce(scrollToSelection), true); } private void scrollToSelection() diff --git a/osu.Game/Screens/OnlinePlay/Header.cs b/osu.Game/Screens/OnlinePlay/Header.cs index 06befe0971..a9e9f046e2 100644 --- a/osu.Game/Screens/OnlinePlay/Header.cs +++ b/osu.Game/Screens/OnlinePlay/Header.cs @@ -38,8 +38,8 @@ namespace osu.Game.Screens.OnlinePlay }; // unnecessary to unbind these as this header has the same lifetime as the screen stack we are attaching to. - stack.ScreenPushed += (_, __) => updateSubScreenTitle(); - stack.ScreenExited += (_, __) => updateSubScreenTitle(); + stack.ScreenPushed += (_, _) => updateSubScreenTitle(); + stack.ScreenExited += (_, _) => updateSubScreenTitle(); } private void updateSubScreenTitle() => title.Screen = stack.CurrentScreen as IOnlinePlaySubScreen; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index 5bdd24221c..474463d5f1 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components base.LoadComplete(); PlaylistItemStats.BindValueChanged(_ => updateCount()); - Playlist.BindCollectionChanged((_, __) => updateCount(), true); + Playlist.BindCollectionChanged((_, _) => updateCount(), true); } private void updateCount() diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 9784e5aace..8a2aeb9e5e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -250,7 +250,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge base.LoadComplete(); ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(passwordTextBox)); - passwordTextBox.OnCommit += (_, __) => performJoin(); + passwordTextBox.OnCommit += (_, _) => performJoin(); } private void performJoin() diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs index d81fee24e0..58ccb24f7a 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public LoungeBackgroundScreen() { SelectedRoom.BindValueChanged(onSelectedRoomChanged); - playlist.BindCollectionChanged((_, __) => PlaylistItem = playlist.GetCurrentItem()); + playlist.BindCollectionChanged((_, _) => PlaylistItem = playlist.GetCurrentItem()); } private void onSelectedRoomChanged(ValueChangedEvent room) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index f7f3c27ede..09bc496da5 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -304,7 +304,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge joiningRoomOperation = ongoingOperationTracker?.BeginOperation(); - RoomManager?.JoinRoom(room, password, r => + RoomManager?.JoinRoom(room, password, _ => { Open(room); joiningRoomOperation?.Dispose(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 20762b2f22..c5d6da1ebc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match switch (room?.Countdown) { - case MatchStartCountdown _: + case MatchStartCountdown: newCountdown = room.Countdown; break; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index b5bb4a766f..b55a7d0731 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { var clickOperation = ongoingOperationTracker.BeginOperation(); - Client.ToggleSpectate().ContinueWith(t => endOperation()); + Client.ToggleSpectate().ContinueWith(_ => endOperation()); void endOperation() => clickOperation?.Dispose(); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistTabControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistTabControl.cs index 9877c15f2f..f7abc91227 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistTabControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylistTabControl.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist protected override void LoadComplete() { base.LoadComplete(); - QueueItems.BindCollectionChanged((_, __) => Text.Text = QueueItems.Count > 0 ? $"Queue ({QueueItems.Count})" : "Queue", true); + QueueItems.BindCollectionChanged((_, _) => Text.Text = QueueItems.Count > 0 ? $"Queue ({QueueItems.Count})" : "Queue", true); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 04342788db..39740e650f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist protected override void LoadComplete() { base.LoadComplete(); - roomPlaylist.BindCollectionChanged((_, __) => InvalidateLayout()); + roomPlaylist.BindCollectionChanged((_, _) => InvalidateLayout()); } public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderBy(item => item.Model.PlaylistOrder); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index ae65e1d969..d49f894ad7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void load() { isConnected.BindTo(client.IsConnected); - isConnected.BindValueChanged(c => Scheduler.AddOnce(poll), true); + isConnected.BindValueChanged(_ => Scheduler.AddOnce(poll), true); } private void poll() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index df0f94688b..a82da7b185 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -125,7 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.GameplayStarted += onGameplayStarted; client.ResultsReady += onResultsReady; - ScoreProcessor.HasCompleted.BindValueChanged(completed => + ScoreProcessor.HasCompleted.BindValueChanged(_ => { // wait for server to tell us that results are ready (see SubmitScore implementation) loadingDisplay.Show(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index e048c12686..8270bb3b6f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(users) { Expanded = { Value = true }, - }, l => + }, _ => { foreach (var instance in instances) leaderboard.AddClock(instance.UserId, instance.GameplayClock); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 1d9ea5f77d..0bf5f2604c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -94,7 +94,7 @@ namespace osu.Game.Screens.OnlinePlay base.LoadComplete(); subScreenSelectedItem?.BindValueChanged(_ => UpdateSelectedItem()); - Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true); + Playlist.BindCollectionChanged((_, _) => UpdateSelectedItem(), true); } protected void UpdateSelectedItem() diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index ff657e16fb..8a9c4db6ad 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } }, true); - Room.MaxAttempts.BindValueChanged(attempts => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); + Room.MaxAttempts.BindValueChanged(_ => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true); } protected override Drawable CreateMainContent() => new Container diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index ca485aa8ea..c1223d7262 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -198,7 +198,7 @@ namespace osu.Game.Screens.Play dropOffScreen(obj, failTime, rotation, originalScale, originalPosition); // need to reapply the fail drop after judgement state changes - obj.ApplyCustomUpdateState += (o, _) => dropOffScreen(obj, failTime, rotation, originalScale, originalPosition); + obj.ApplyCustomUpdateState += (_, _) => dropOffScreen(obj, failTime, rotation, originalScale, originalPosition); appliedObjects.Add(obj); } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 99b9362700..c2652e9212 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Play }, }; - State.ValueChanged += s => InternalButtons.Deselect(); + State.ValueChanged += _ => InternalButtons.Deselect(); updateRetryCount(); } @@ -267,7 +267,7 @@ namespace osu.Game.Screens.Play { switch (e) { - case ScrollEvent _: + case ScrollEvent: if (ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) return globalAction.TriggerEvent(e); diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index aa9a2ab242..39a8f1e783 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -218,7 +218,7 @@ namespace osu.Game.Screens.Play.HUD overlayCircle.ScaleTo(0, 100) .Then().FadeOut().ScaleTo(1).FadeIn(500) - .OnComplete(a => + .OnComplete(_ => { icon.ScaleTo(1, 100); circularProgress.FadeOut(100).OnComplete(_ => diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index fe27e1fc24..8f80644d52 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -352,7 +352,7 @@ namespace osu.Game.Screens.Play // When the scoring mode changes, relative positions of elements may change (see DefaultSkin.GetDrawableComponent). // This is a best effort implementation for cases where users haven't customised layouts. scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); - scoringMode.BindValueChanged(val => Reload()); + scoringMode.BindValueChanged(_ => Reload()); } } } diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index ef28632d76..b6094726c0 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -157,10 +157,10 @@ namespace osu.Game.Screens.Play { switch (e) { - case KeyDownEvent _: - case KeyUpEvent _: - case MouseDownEvent _: - case MouseUpEvent _: + case KeyDownEvent: + case KeyUpEvent: + case MouseDownEvent: + case MouseUpEvent: return Target.Children.Any(c => c.TriggerEvent(e)); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4a8460ff46..4040adc48d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Play PrepareReplay(); - ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); + ScoreProcessor.NewJudgement += _ => ScoreProcessor.PopulateScore(Score.ScoreInfo); ScoreProcessor.OnResetFromReplayFrame += () => ScoreProcessor.PopulateScore(Score.ScoreInfo); gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); @@ -315,7 +315,7 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Start(); }); - DrawableRuleset.IsPaused.BindValueChanged(paused => + DrawableRuleset.IsPaused.BindValueChanged(_ => { updateGameplayState(); updateSampleDisabledState(); diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 2a5e356df8..64b4853a67 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play protected override bool CheckModsAllowFailure() => false; public ReplayPlayer(Score score, PlayerConfiguration configuration = null) - : this((_, __) => score, configuration) + : this((_, _) => score, configuration) { } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs index 5d7b04743e..3505786b64 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Spacing = new Vector2(10, 0), Children = new[] { - base.CreateContent().With(d => + base.CreateContent().With(_ => { Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 384f4c4d4f..ab68dec92d 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Ranking.Statistics Task.Run(() => { playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); - }, loadCancellation.Token).ContinueWith(t => Schedule(() => + }, loadCancellation.Token).ContinueWith(_ => Schedule(() => { bool hitEventsAvailable = newScore.HitEvents.Count != 0; Container container; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 87a1fb4ccb..72e6c7d159 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -959,7 +959,7 @@ namespace osu.Game.Screens.Select { // root should always remain selected. if not, PerformSelection will not be called. State.Value = CarouselItemState.Selected; - State.ValueChanged += state => State.Value = CarouselItemState.Selected; + State.ValueChanged += _ => State.Value = CarouselItemState.Selected; this.carousel = carousel; } diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index dfcd43189b..bf6803f551 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Select { switch (tab) { - case BeatmapDetailAreaDetailTabItem _: + case BeatmapDetailAreaDetailTabItem: Details.Show(); break; diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index fdc0d4efdd..2fe62c4a4e 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -217,7 +217,7 @@ namespace osu.Game.Screens.Select }); }; - lookup.Failure += e => + lookup.Failure += _ => { Schedule(() => { diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index a948e5b0bd..cc01f61c57 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Carousel + $" && {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $2" + $" && {nameof(ScoreInfo.DeletePending)} == false", api.LocalUser.Value.Id, beatmapInfo.ID, ruleset.Value.ShortName) .OrderByDescending(s => s.TotalScore), - (items, changes, ___) => + (items, _, _) => { Rank = items.FirstOrDefault()?.Rank; // Required since presence is changed via IsPresent override diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 59eb3fa607..693f182065 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -96,7 +96,7 @@ namespace osu.Game.Screens.Select.Details modSettingChangeTracker?.Dispose(); modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); - modSettingChangeTracker.SettingChanged += m => + modSettingChangeTracker.SettingChanged += _ => { debouncedStatisticsUpdate?.Cancel(); debouncedStatisticsUpdate = Scheduler.AddDelayed(updateStatistics, 100); diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs index 4d3c844753..a19acddb48 100644 --- a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select { switch (item) { - case BeatmapDetailAreaDetailTabItem _: + case BeatmapDetailAreaDetailTabItem: return TabType.Details; case BeatmapDetailAreaLeaderboardTabItem leaderboardTab: diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 43b3cb0aa4..2467b7d039 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -74,7 +74,7 @@ namespace osu.Game.Skinning switch (target.Target) { case SkinnableTarget.SongSelect: - var songSelectComponents = new SkinnableTargetComponentsContainer(container => + var songSelectComponents = new SkinnableTargetComponentsContainer(_ => { // do stuff when we need to. }); diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 7e9f1d0e8e..649b63dda4 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -190,13 +190,13 @@ namespace osu.Game.Skinning.Editor // schedule ensures this only happens when the skin editor is visible. // also avoid some weird endless recursion / bindable feedback loop (something to do with tracking skins across three different bindable types). // probably something which will be factored out in a future database refactor so not too concerning for now. - currentSkin.BindValueChanged(skin => + currentSkin.BindValueChanged(_ => { hasBegunMutating = false; Scheduler.AddOnce(skinChanged); }, true); - SelectedComponents.BindCollectionChanged((_, __) => Scheduler.AddOnce(populateSettings), true); + SelectedComponents.BindCollectionChanged((_, _) => Scheduler.AddOnce(populateSettings), true); } public void UpdateTargetScreen(Drawable targetScreen) diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index de33d49941..000917f728 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Skinning.Editor var editor = new SkinEditor(); - editor.State.BindValueChanged(visibility => updateComponentVisibility()); + editor.State.BindValueChanged(_ => updateComponentVisibility()); skinEditor = editor; diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 2aaf0ba18e..49914c53aa 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -120,16 +120,16 @@ namespace osu.Game.Skinning currentConfig.MinimumColumnWidth = minWidth; break; - case string _ when pair.Key.StartsWith("Colour", StringComparison.Ordinal): + case string when pair.Key.StartsWith("Colour", StringComparison.Ordinal): HandleColours(currentConfig, line); break; // Custom sprite paths - case string _ when pair.Key.StartsWith("NoteImage", StringComparison.Ordinal): - case string _ when pair.Key.StartsWith("KeyImage", StringComparison.Ordinal): - case string _ when pair.Key.StartsWith("Hit", StringComparison.Ordinal): - case string _ when pair.Key.StartsWith("Stage", StringComparison.Ordinal): - case string _ when pair.Key.StartsWith("Lighting", StringComparison.Ordinal): + case string when pair.Key.StartsWith("NoteImage", StringComparison.Ordinal): + case string when pair.Key.StartsWith("KeyImage", StringComparison.Ordinal): + case string when pair.Key.StartsWith("Hit", StringComparison.Ordinal): + case string when pair.Key.StartsWith("Stage", StringComparison.Ordinal): + case string when pair.Key.StartsWith("Lighting", StringComparison.Ordinal): currentConfig.ImageLookups[pair.Key] = pair.Value; break; } diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index 1b5de9abb5..ed31a8c3f5 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -214,7 +214,7 @@ namespace osu.Game.Skinning { try { - realm.Write(r => skin.Hash = ComputeHash(skin)); + realm.Write(_ => skin.Hash = ComputeHash(skin)); } catch (Exception e) { diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index d7399b0141..e447570931 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -269,9 +269,9 @@ namespace osu.Game.Skinning { switch (lookup) { - case GlobalSkinColours _: - case SkinComboColourLookup _: - case SkinCustomColourLookup _: + case GlobalSkinColours: + case SkinComboColourLookup: + case SkinCustomColourLookup: if (provider.AllowColourLookup) return skin.GetConfig(lookup); diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 838dc618f2..a759482b5d 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -109,20 +109,20 @@ namespace osu.Game.Storyboards generateCommands(generated, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing)); generateCommands(generated, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing)); generateCommands(generated, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); - generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration), + generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, _) => d.TransformBlendingMode(value, duration), false); if (drawable is IVectorScalable vectorScalable) { - generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (d, value) => vectorScalable.VectorScale = value, - (d, value, duration, easing) => vectorScalable.VectorScaleTo(value, duration, easing)); + generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (_, value) => vectorScalable.VectorScale = value, + (_, value, duration, easing) => vectorScalable.VectorScaleTo(value, duration, easing)); } if (drawable is IFlippable flippable) { - generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration), + generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (_, value) => flippable.FlipH = value, (_, value, duration, _) => flippable.TransformFlipH(value, duration), false); - generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration), + generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (_, value) => flippable.FlipV = value, (_, value, duration, _) => flippable.TransformFlipV(value, duration), false); } diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index 9adf6458cd..803db07fa0 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -49,8 +49,8 @@ namespace osu.Game.Tests.Visual } }); - Stack.ScreenPushed += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}"); - Stack.ScreenExited += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed ← {newScreen}"); + Stack.ScreenPushed += (_, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}"); + Stack.ScreenExited += (_, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed ← {newScreen}"); } protected void LoadScreen(OsuScreen screen) => Stack.Push(screen); From 81bfe1406386a8c40132ccdc39afefdbf5049609 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 21:28:24 +0900 Subject: [PATCH 154/363] Update `DotSettings` to match framework (in most cases) --- osu.sln.DotSettings | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 8fd750a50d..32dbb62e5e 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -7,19 +7,22 @@ ExplicitlyExcluded SOLUTION WARNING + WARNING + WARNING WARNING WARNING HINT HINT WARNING + WARNING + WARNING WARNING True WARNING WARNING HINT DO_NOT_SHOW - WARNING - WARNING + HINT HINT HINT WARNING @@ -58,14 +61,17 @@ HINT HINT HINT - WARNING + HINT + DO_NOT_SHOW WARNING WARNING WARNING WARNING + DO_NOT_SHOW WARNING WARNING WARNING + DO_NOT_SHOW WARNING WARNING WARNING @@ -73,7 +79,7 @@ WARNING HINT WARNING - HINT + DO_NOT_SHOW DO_NOT_SHOW WARNING DO_NOT_SHOW @@ -107,7 +113,6 @@ HINT HINT WARNING - DO_NOT_SHOW DO_NOT_SHOW WARNING WARNING @@ -118,16 +123,13 @@ WARNING WARNING WARNING - HINT + HINT WARNING WARNING WARNING WARNING HINT - HINT WARNING - HINT - HINT HINT HINT HINT @@ -135,8 +137,10 @@ HINT HINT WARNING + HINT + HINT WARNING - WARNING + HINT WARNING WARNING WARNING @@ -145,6 +149,7 @@ DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW + WARNING WARNING WARNING WARNING @@ -213,8 +218,7 @@ WARNING SUGGESTION DO_NOT_SHOW - - True + HINT DO_NOT_SHOW WARNING WARNING @@ -230,8 +234,8 @@ HINT HINT HINT - HINT HINT + HINT HINT DO_NOT_SHOW WARNING @@ -265,6 +269,7 @@ Explicit ExpressionBody BlockBody + ExplicitlyTyped True NEXT_LINE True From 14327943a2d0eb550ed1d4f8bddaef9a2c8d8246 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Jun 2022 21:28:30 +0900 Subject: [PATCH 155/363] 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 f2790d2520..9cd1709211 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 0802a98858..9e7c0020c3 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 a272df20d4..5315831a33 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From df30bedab6f14d45466dd1c6391dec1ad99bf871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Jun 2022 16:01:52 +0200 Subject: [PATCH 156/363] Update framework again --- 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 9cd1709211..8f4a102cce 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 9e7c0020c3..b9033f1bb9 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 5315831a33..a1e4cf3ba0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From a83c45b50e4f126bfa218d4bd8e4d8a2bf1e5145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Jun 2022 16:07:14 +0200 Subject: [PATCH 157/363] Fix compile failures from framework breaking change --- osu.Game.Tournament/TournamentGameBase.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index ccbe77fa95..75c9f17d4c 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tournament dependencies.Cache(new TournamentVideoResourceStore(storage)); - Textures.AddStore(new TextureLoaderStore(new StorageBackedResourceStore(storage))); + Textures.AddTextureSource(new TextureLoaderStore(new StorageBackedResourceStore(storage))); dependencies.CacheAs(new StableInfo(storage)); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b0ba62a6ac..7d39aad226 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -244,7 +244,7 @@ namespace osu.Game dependencies.CacheAs(Storage); var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures"))); - largeStore.AddStore(Host.CreateTextureLoaderStore(new OnlineStore())); + largeStore.AddTextureSource(Host.CreateTextureLoaderStore(new OnlineStore())); dependencies.Cache(largeStore); dependencies.CacheAs(this); From e0c2228b4181d707702873c9d4943003debb7b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 24 Jun 2022 16:40:35 +0200 Subject: [PATCH 158/363] Revert downgrades in code inspection settings --- osu.sln.DotSettings | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 32dbb62e5e..a55b3b5074 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -22,7 +22,8 @@ WARNING HINT DO_NOT_SHOW - HINT + WARNING + WARNING HINT HINT WARNING @@ -61,7 +62,7 @@ HINT HINT HINT - HINT + WARNING DO_NOT_SHOW WARNING WARNING @@ -79,7 +80,7 @@ WARNING HINT WARNING - DO_NOT_SHOW + HINT DO_NOT_SHOW WARNING DO_NOT_SHOW @@ -113,6 +114,7 @@ HINT HINT WARNING + DO_NOT_SHOW DO_NOT_SHOW WARNING WARNING @@ -129,7 +131,10 @@ WARNING WARNING HINT + HINT WARNING + HINT + HINT HINT HINT HINT @@ -140,7 +145,7 @@ HINT HINT WARNING - HINT + WARNING WARNING WARNING WARNING @@ -218,7 +223,8 @@ WARNING SUGGESTION DO_NOT_SHOW - HINT + + True DO_NOT_SHOW WARNING WARNING @@ -234,8 +240,8 @@ HINT HINT HINT - HINT HINT + HINT HINT DO_NOT_SHOW WARNING From a039f9878a6b4eca158982dce24fdd4ca0dd8584 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 25 Jun 2022 19:33:44 +0900 Subject: [PATCH 159/363] Adjust menu cursor rotation to follow mouse, rather than be fixated around original click location --- osu.Game/Graphics/Cursor/MenuCursor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 4544633318..b387dbf9a8 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -51,6 +51,9 @@ namespace osu.Game.Graphics.Cursor { if (dragRotationState != DragRotationState.NotDragging) { + if (Vector2.Distance(positionMouseDown, e.MousePosition) > 100) + positionMouseDown = Interpolation.ValueAt(0.001f, positionMouseDown, e.MousePosition, 0, Clock.ElapsedFrameTime); + var position = e.MousePosition; float distance = Vector2Extensions.Distance(position, positionMouseDown); From 2ecab454aa4dbde8ca37f2d07a0f93be80e9dff0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 25 Jun 2022 19:43:39 +0900 Subject: [PATCH 160/363] Adjust animations and tolerances to make things feel snappier --- osu.Game/Graphics/Cursor/MenuCursor.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index b387dbf9a8..10ed76ebdd 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -51,15 +51,16 @@ namespace osu.Game.Graphics.Cursor { if (dragRotationState != DragRotationState.NotDragging) { - if (Vector2.Distance(positionMouseDown, e.MousePosition) > 100) - positionMouseDown = Interpolation.ValueAt(0.001f, positionMouseDown, e.MousePosition, 0, Clock.ElapsedFrameTime); + // make the rotation centre point floating. + if (Vector2.Distance(positionMouseDown, e.MousePosition) > 60) + positionMouseDown = Interpolation.ValueAt(0.005f, positionMouseDown, e.MousePosition, 0, Clock.ElapsedFrameTime); var position = e.MousePosition; float distance = Vector2Extensions.Distance(position, positionMouseDown); // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. - if (dragRotationState == DragRotationState.DragStarted && distance > 30) + if (dragRotationState == DragRotationState.DragStarted && distance > 80) dragRotationState = DragRotationState.Rotating; // don't rotate when distance is zero to avoid NaN @@ -74,7 +75,7 @@ namespace osu.Game.Graphics.Cursor if (diff > 180) diff -= 360; degrees = activeCursor.Rotation + diff; - activeCursor.RotateTo(degrees, 600, Easing.OutQuint); + activeCursor.RotateTo(degrees, 120, Easing.OutQuint); } } @@ -114,7 +115,7 @@ namespace osu.Game.Graphics.Cursor if (dragRotationState != DragRotationState.NotDragging) { - activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); + activeCursor.RotateTo(0, 400 * (0.5f + Math.Abs(activeCursor.Rotation / 960)), Easing.OutElasticQuarter); dragRotationState = DragRotationState.NotDragging; } From 1a22377e19d45ee88dab9e9effba1260b7bacf47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 26 Jun 2022 16:42:28 +0900 Subject: [PATCH 161/363] Fix test deadlock due to TPL threadpool saturation As found in aggressive CI runs: ```csharp 00007F6F527F32B0 00007f74f937c72d (MethodDesc 00007f74f5545a78 + 0x22d System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken)), calling (MethodDesc 00007f74f51c0838 + 0 System.Threading.Monitor.Wait(System.Object, Int32)) 00007F6F527F3330 00007f74f93861ca (MethodDesc 00007f74f5259100 + 0x10a System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken)), calling (MethodDesc 00007f74f5545a78 + 0 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken)) 00007F6F527F3390 00007f74f9385d11 (MethodDesc 00007f74f52590e8 + 0x81 System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken)), calling (MethodDesc 00007f74f5259100 + 0 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken)) 00007F6F527F33E0 00007f74fc3823af (MethodDesc 00007f74f5934c08 + 0x4f System.Threading.Tasks.Task`1[[System.Boolean, System.Private.CoreLib]].GetResultCore(Boolean)), calling (MethodDesc 00007f74f52590e8 + 0 System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken)) 00007F6F527F3400 00007f74fb0aad92 (MethodDesc 00007f74fb28b000 + 0x122 osu.Game.Tests.Visual.OnlinePlay.OnlinePlayTestScene+<>c__DisplayClass21_0.b__1(osu.Game.Online.API.APIRequest)), calling (MethodDesc 00007f74f5934be8 + 0 System.Threading.Tasks.Task`1[[System.Boolean, System.Private.CoreLib]].get_Result()) 00007F6F527F3470 00007f74fa9c6dd7 (MethodDesc 00007f74f921f6c8 + 0x77 osu.Game.Online.API.DummyAPIAccess.PerformAsync(osu.Game.Online.API.APIRequest)) 00007F6F527F34C0 00007f74fb13c718 (MethodDesc 00007f74fb4e0c50 + 0x538 osu.Game.Database.OnlineLookupCache`3+d__13[[System.Int32, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]].MoveNext()) ``` Going with the simplest solution, as what we had was pretty ugly to start with and I don't want to overthink this, just push a fix and hope for the best. --- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 282312c9c1..6577057c17 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -4,14 +4,14 @@ #nullable disable using System; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Database; +using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; @@ -72,22 +72,18 @@ namespace osu.Game.Tests.Visual.OnlinePlay ((DummyAPIAccess)API).HandleRequest = request => { - TaskCompletionSource tcs = new TaskCompletionSource(); - - // Because some of the handlers use realm, we need to ensure the game is still alive when firing. - // If we don't, a stray `PerformAsync` could hit an `ObjectDisposedException` if running too late. - Scheduler.Add(() => + try { - bool result = handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); - tcs.SetResult(result); - }, false); - -#pragma warning disable RS0030 - // We can't GetResultSafely() here (will fail with "Can't use GetResultSafely from inside an async operation."), but Wait is safe enough due to - // the task being a TaskCompletionSource. - // Importantly, this doesn't deadlock because of the scheduler call above running inline where feasible (see the `false` argument). - return tcs.Task.Result; -#pragma warning restore RS0030 + return handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); + } + catch (ObjectDisposedException) + { + // These requests can be fired asynchronously, but potentially arrive after game components + // have been disposed (ie. realm in BeatmapManager). + // This only happens in tests and it's easiest to ignore them for now. + Logger.Log($"Handled {nameof(ObjectDisposedException)} in test request handling"); + return true; + } }; }); From cd9eaf215b9d3c9a10271d008a8a3baa25f0407c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 02:58:01 +0900 Subject: [PATCH 162/363] Attempt to fix flaky `PlaylistOverlay` test --- .../Visual/UserInterface/TestScenePlaylistOverlay.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 4c35ec40b5..ee5ef2f364 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -27,6 +27,8 @@ namespace osu.Game.Tests.Visual.UserInterface private Live first; + private const int item_count = 100; + [SetUp] public void Setup() => Schedule(() => { @@ -46,7 +48,7 @@ namespace osu.Game.Tests.Visual.UserInterface beatmapSets.Clear(); - for (int i = 0; i < 100; i++) + for (int i = 0; i < item_count; i++) { beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo().ToLiveUnmanaged()); } @@ -59,6 +61,13 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestRearrangeItems() { + AddUntilStep("wait for load complete", () => + { + return this + .ChildrenOfType() + .Count(i => i.ChildrenOfType().First().DelayedLoadCompleted) > 6; + }); + AddUntilStep("wait for animations to complete", () => !playlistOverlay.Transforms.Any()); AddStep("hold 1st item handle", () => From b5a8889fb80e359d0afb17a7d1e76dc4b66df941 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 03:04:49 +0900 Subject: [PATCH 163/363] Attempt to fix flaky `EditorNavigation` test Conditionals taken from https://github.com/ppy/osu/blob/f8830c6850128456266c82de83273204f8b74ac0/osu.Game/Tests/Visual/EditorTestScene.cs#L61-L62 where it seemed to resolve other similar cases. --- osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs index 85b50a9b21..273e8abcd7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs @@ -9,10 +9,12 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Select; using osu.Game.Tests.Resources; @@ -38,7 +40,10 @@ namespace osu.Game.Tests.Visual.Editing AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor + && editor.IsLoaded + && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddStep("test gameplay", () => { var testGameplayButton = this.ChildrenOfType().Single(); From da61d0547ffb39614a6c7b93610ac26c77e1fc67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 15:24:25 +0900 Subject: [PATCH 164/363] Include archive name in import log output --- osu.Game/Database/RealmArchiveModelImporter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index a3349d9918..76f6db1384 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -296,7 +296,8 @@ namespace osu.Game.Database try { - LogForModel(item, @"Beginning import..."); + // Log output here will be missing a valid hash in non-batch imports. + LogForModel(item, $@"Beginning import from {archive?.Name ?? "unknown"}..."); // TODO: do we want to make the transaction this local? not 100% sure, will need further investigation. using (var transaction = realm.BeginWrite()) From 13c8d330094370093b33f724d84f4a914761e528 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 15:29:01 +0900 Subject: [PATCH 165/363] Fix second case of empty beatmaps being reported to sentry as errors --- osu.Game/Beatmaps/BeatmapImporter.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 237088036c..d89541844b 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -18,7 +18,6 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; using osu.Game.IO.Archives; -using osu.Game.Models; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Skinning; @@ -49,7 +48,7 @@ namespace osu.Game.Beatmaps protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) { if (archive != null) - beatmapSet.Beatmaps.AddRange(createBeatmapDifficulties(beatmapSet.Files, realm)); + beatmapSet.Beatmaps.AddRange(createBeatmapDifficulties(beatmapSet, realm)); foreach (BeatmapInfo b in beatmapSet.Beatmaps) { @@ -200,23 +199,32 @@ namespace osu.Game.Beatmaps /// /// Create all required s for the provided archive. /// - private List createBeatmapDifficulties(IList files, Realm realm) + private List createBeatmapDifficulties(BeatmapSetInfo beatmapSet, Realm realm) { var beatmaps = new List(); - foreach (var file in files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase))) + foreach (var file in beatmapSet.Files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase))) { using (var memoryStream = new MemoryStream(Files.Store.Get(file.File.GetStoragePath()))) // we need a memory stream so we can seek { IBeatmap decoded; + using (var lineReader = new LineBufferedReader(memoryStream, true)) + { + if (lineReader.PeekLine() == null) + { + LogForModel(beatmapSet, $"No content found in beatmap file {file.Filename}."); + continue; + } + decoded = Decoder.GetDecoder(lineReader).Decode(lineReader); + } string hash = memoryStream.ComputeSHA2Hash(); if (beatmaps.Any(b => b.Hash == hash)) { - Logger.Log($"Skipping import of {file.Filename} due to duplicate file content.", LoggingTarget.Database); + LogForModel(beatmapSet, $"Skipping import of {file.Filename} due to duplicate file content."); continue; } @@ -227,7 +235,7 @@ namespace osu.Game.Beatmaps if (ruleset?.Available != true) { - Logger.Log($"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.Ruleset.OnlineID}.", LoggingTarget.Database); + LogForModel(beatmapSet, $"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.Ruleset.OnlineID}."); continue; } From 01487a11859af1cd36719fe250d34dff919c66cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 15:52:45 +0900 Subject: [PATCH 166/363] Add assertion on realm re-fetch being not-null --- osu.Game/Database/ModelManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs index 9603412178..4224c92f2c 100644 --- a/osu.Game/Database/ModelManager.cs +++ b/osu.Game/Database/ModelManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using osu.Framework.Platform; @@ -46,6 +47,7 @@ namespace osu.Game.Database Realm.Realm.Write(realm => { var managed = realm.Find(item.ID); + Debug.Assert(managed != null); operation(managed); item.Files.Clear(); From b70b365411ab9c820da30829b6dbcc5c42c3b15a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 15:53:05 +0900 Subject: [PATCH 167/363] Ensure files are cleaned up even on test step failure in `TestAddAudioTrack` --- .../Editing/TestSceneEditorBeatmapCreation.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index a204fc5686..fcbf63da79 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -95,18 +95,23 @@ namespace osu.Game.Tests.Visual.Editing string extractedFolder = $"{temp}_extracted"; Directory.CreateDirectory(extractedFolder); - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(extractedFolder); + try + { + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); - bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); + bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); - File.Delete(temp); - Directory.Delete(extractedFolder, true); + // ensure audio file is copied to beatmap as "audio.mp3" rather than original filename. + Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - // ensure audio file is copied to beatmap as "audio.mp3" rather than original filename. - Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - - return success; + return success; + } + finally + { + File.Delete(temp); + Directory.Delete(extractedFolder, true); + } }); AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000); From 10d1bbb1316f427598d2cd714ebc7b645061e030 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 15:58:51 +0900 Subject: [PATCH 168/363] Log global working beatmap updates in `OsuGameBase` instead of `OsuGame` --- osu.Game/OsuGame.cs | 1 - osu.Game/OsuGameBase.cs | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f51a18b36f..7724241d5a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -612,7 +612,6 @@ namespace osu.Game { beatmap.OldValue?.CancelAsyncLoad(); beatmap.NewValue?.BeginAsyncLoad(); - Logger.Log($"Game-wide working beatmap updated to {beatmap.NewValue}"); } private void modsChanged(ValueChangedEvent> mods) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7d39aad226..57369419d2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -490,10 +490,12 @@ namespace osu.Game } } - private void onBeatmapChanged(ValueChangedEvent valueChangedEvent) + private void onBeatmapChanged(ValueChangedEvent beatmap) { if (IsLoaded && !ThreadSafety.IsUpdateThread) throw new InvalidOperationException("Global beatmap bindable must be changed from update thread."); + + Logger.Log($"Game-wide working beatmap updated to {beatmap.NewValue}"); } private void onRulesetChanged(ValueChangedEvent r) From f6a6538e9698f402ce612b8abecef67ec533aac5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 27 Jun 2022 15:01:53 +0900 Subject: [PATCH 169/363] Add more properties to IBeatmapOnlineInfo --- osu.Game/Beatmaps/IBeatmapOnlineInfo.cs | 33 ++++++++++++++++--- .../API/Requests/Responses/APIBeatmap.cs | 3 ++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs index 160b7cf0ae..e1634e7d24 100644 --- a/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs @@ -13,25 +13,50 @@ namespace osu.Game.Beatmaps /// int? MaxCombo { get; } + /// + /// The approach rate. + /// + float ApproachRate { get; } + + /// + /// The circle size. + /// + float CircleSize { get; } + + /// + /// The drain rate. + /// + float DrainRate { get; } + + /// + /// The overall difficulty. + /// + float OverallDifficulty { get; } + /// /// The amount of circles in this beatmap. /// - public int CircleCount { get; } + int CircleCount { get; } /// /// The amount of sliders in this beatmap. /// - public int SliderCount { get; } + int SliderCount { get; } + + /// + /// The amount of spinners in tihs beatmap. + /// + int SpinnerCount { get; } /// /// The amount of plays this beatmap has. /// - public int PlayCount { get; } + int PlayCount { get; } /// /// The amount of passes this beatmap has. /// - public int PassCount { get; } + int PassCount { get; } APIFailTimes? FailTimes { get; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 258708de2b..735fde333d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -69,6 +69,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"count_sliders")] public int SliderCount { get; set; } + [JsonProperty(@"count_spinners")] + public int SpinnerCount { get; set; } + [JsonProperty(@"version")] public string DifficultyName { get; set; } = string.Empty; From 0579780bb8c38e25675755a4bc5cf045c2e1e7aa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 27 Jun 2022 15:05:49 +0900 Subject: [PATCH 170/363] Add IBeatmapOnlineInfo parameter and use to extract more data --- .../Difficulty/CatchDifficultyAttributes.cs | 5 +++-- .../Difficulty/ManiaDifficultyAttributes.cs | 5 +++-- .../Difficulty/OsuDifficultyAttributes.cs | 10 ++++++++-- .../Difficulty/TaikoDifficultyAttributes.cs | 5 +++-- osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 4 +++- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index 9951d736c3..c2f5aa3629 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Newtonsoft.Json; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; namespace osu.Game.Rulesets.Catch.Difficulty @@ -31,9 +32,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty yield return (ATTRIB_ID_MAX_COMBO, MaxCombo); } - public override void FromDatabaseAttributes(IReadOnlyDictionary values) + public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) { - base.FromDatabaseAttributes(values); + base.FromDatabaseAttributes(values, onlineInfo); StarRating = values[ATTRIB_ID_AIM]; ApproachRate = values[ATTRIB_ID_APPROACH_RATE]; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 8a5161be79..023c45fbcf 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Newtonsoft.Json; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; namespace osu.Game.Rulesets.Mania.Difficulty @@ -37,9 +38,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier); } - public override void FromDatabaseAttributes(IReadOnlyDictionary values) + public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) { - base.FromDatabaseAttributes(values); + base.FromDatabaseAttributes(values, onlineInfo); MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; StarRating = values[ATTRIB_ID_DIFFICULTY]; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 19217015c1..abab44b96a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -96,9 +97,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor); } - public override void FromDatabaseAttributes(IReadOnlyDictionary values) + public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) { - base.FromDatabaseAttributes(values); + base.FromDatabaseAttributes(values, onlineInfo); AimDifficulty = values[ATTRIB_ID_AIM]; SpeedDifficulty = values[ATTRIB_ID_SPEED]; @@ -108,6 +109,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty StarRating = values[ATTRIB_ID_DIFFICULTY]; FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; + + DrainRate = onlineInfo.DrainRate; + HitCircleCount = onlineInfo.CircleCount; + SliderCount = onlineInfo.SliderCount; + SpinnerCount = onlineInfo.SpinnerCount; } #region Newtonsoft.Json implicit ShouldSerialize() methods diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index 6c617a22a4..eec59afc5b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Newtonsoft.Json; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; namespace osu.Game.Rulesets.Taiko.Difficulty @@ -57,9 +58,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); } - public override void FromDatabaseAttributes(IReadOnlyDictionary values) + public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) { - base.FromDatabaseAttributes(values); + base.FromDatabaseAttributes(values, onlineInfo); MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; StarRating = values[ATTRIB_ID_DIFFICULTY]; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index e0844a5c8a..1f08790408 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Difficulty @@ -74,7 +75,8 @@ namespace osu.Game.Rulesets.Difficulty /// Reads osu-web database attribute mappings into this object. /// /// The attribute mappings. - public virtual void FromDatabaseAttributes(IReadOnlyDictionary values) + /// The where more information about the beatmap may be extracted from (such as AR/CS/OD/etc). + public virtual void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo) { } } From 0fd2c010e5f8de3ec1ba777925d426dfcb63b55e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 27 Jun 2022 15:06:42 +0900 Subject: [PATCH 171/363] Remove NRT disables from attributes classes --- .../Difficulty/CatchDifficultyAttributes.cs | 2 -- .../Difficulty/ManiaDifficultyAttributes.cs | 2 -- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs | 2 -- .../Difficulty/TaikoDifficultyAttributes.cs | 2 -- osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs | 5 ++--- 5 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index c2f5aa3629..2d01153f98 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using Newtonsoft.Json; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 023c45fbcf..680732f26e 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using Newtonsoft.Json; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index abab44b96a..4b06502e32 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index eec59afc5b..380ab4a4fc 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using Newtonsoft.Json; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 1f08790408..b0e83d6abe 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; @@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// The mods which were applied to the beatmap. /// - public Mod[] Mods { get; set; } + public Mod[] Mods { get; set; } = Array.Empty(); /// /// The combined star rating of all skills. From 5a7d339cc871aeacaf1f2623f289eb2b599e7253 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 16:19:31 +0900 Subject: [PATCH 172/363] Centralise and harden editor-ready-for-use check Not only does this combine the check into one location, but it also adds a check on the global `WorkingBeatmap` being updated, which is the only way I can see the following failure happening: ```csharp 05:19:07 osu.Game.Tests: osu.Game.Tests.Visual.Editing.TestSceneEditorBeatmapCreation.TestAddAudioTrack 05:19:07 Failed TestAddAudioTrack [161 ms] 05:19:07 Error Message: 05:19:07 TearDown : System.NullReferenceException : Object reference not set to an instance of an object. 05:19:07 Stack Trace: 05:19:07 --TearDown 05:19:07 at osu.Game.Database.ModelManager`1.<>c__DisplayClass7_0.b__0(TModel managed) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Database\ModelManager.cs:line 36 05:19:07 at osu.Game.Database.ModelManager`1.<>c__DisplayClass8_0.b__0(Realm realm) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Database\ModelManager.cs:line 49 05:19:07 at osu.Game.Database.RealmExtensions.Write(Realm realm, Action`1 function) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Database\RealmExtensions.cs:line 14 05:19:07 at osu.Game.Database.ModelManager`1.performFileOperation(TModel item, Action`1 operation) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Database\ModelManager.cs:line 46 05:19:07 at osu.Game.Database.ModelManager`1.AddFile(TModel item, Stream contents, String filename) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Database\ModelManager.cs:line 36 05:19:07 at osu.Game.Screens.Edit.Setup.ResourcesSection.ChangeAudioTrack(FileInfo source) in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Screens\Edit\Setup\ResourcesSection.cs:line 115 05:19:07 at osu.Game.Tests.Visual.Editing.TestSceneEditorBeatmapCreation.b__13_0() in C:\BuildAgent\work\ecd860037212ac52\osu.Game.Tests\Visual\Editing\TestSceneEditorBeatmapCreation.cs:line 101 05:19:07 at osu.Framework.Testing.Drawables.Steps.AssertButton.checkAssert() 05:19:07 at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) 05:19:07 at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) ``` --- .../Editor/TestSceneSliderVelocityAdjust.cs | 9 ++---- .../Editing/TestSceneEditorBeatmapCreation.cs | 2 -- .../Editing/TestSceneEditorNavigation.cs | 7 +---- osu.Game/Screens/Edit/Editor.cs | 29 +++++++++++++++++++ osu.Game/Tests/Visual/EditorTestScene.cs | 8 ++--- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs index 3d9fe37e0f..ef9e332253 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs @@ -10,7 +10,6 @@ using osu.Framework.Input; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; @@ -41,10 +40,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); - private bool editorComponentsReady => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true - && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true - && editor?.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; - [TestCase(true)] [TestCase(false)] public void TestVelocityChangeSavesCorrectly(bool adjustVelocity) @@ -52,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor double? velocity = null; AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader())); - AddUntilStep("wait for editor load", () => editorComponentsReady); + AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time)); AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3)); @@ -91,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("exit", () => InputManager.Key(Key.Escape)); AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader())); - AddUntilStep("wait for editor load", () => editorComponentsReady); + AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true); AddStep("seek to slider", () => editorClock.Seek(slider.StartTime)); AddAssert("slider has correct velocity", () => slider.Velocity == velocity); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index fcbf63da79..b711d55e15 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Editing { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - protected override bool EditorComponentsReady => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true; - protected override bool IsolateSavingFromDatabase => false; [Resolved] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs index 273e8abcd7..62ca97d5e0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs @@ -9,12 +9,10 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; -using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Select; using osu.Game.Tests.Resources; @@ -40,10 +38,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor - && editor.IsLoaded - && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true - && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); AddStep("test gameplay", () => { var testGameplayButton = this.ChildrenOfType().Single(); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 20bdf75b7d..9dbb5f4cfe 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -21,6 +21,7 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -39,6 +40,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Design; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Edit.Setup; @@ -97,6 +99,32 @@ namespace osu.Game.Screens.Edit public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; + /// + /// Ensure all asynchronously loading pieces of the editor are in a good state. + /// This exists here for convenience for tests, not for actual use. + /// Eventually we'd probably want a better way to signal this. + /// + public bool ReadyForUse + { + get + { + if (!workingBeatmapUpdated) + return false; + + var loadedScreen = screenContainer?.Children.SingleOrDefault(s => s.IsLoaded); + + if (loadedScreen == null) + return false; + + if (loadedScreen is EditorScreenWithTimeline) + return loadedScreen.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + + return true; + } + } + + private bool workingBeatmapUpdated; + private readonly Bindable samplePlaybackDisabled = new Bindable(); private bool canSave; @@ -219,6 +247,7 @@ namespace osu.Game.Screens.Edit // this assumes that nothing during the rest of this load() method is accessing Beatmap.Value (loadableBeatmap should be preferred). // generally this is quite safe, as the actual load of editor content comes after menuBar.Mode.ValueChanged is fired in its own LoadComplete. Beatmap.Value = loadableBeatmap; + workingBeatmapUpdated = true; }); OsuMenuItem undoMenuItem; diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 3f5fee5768..31036247ab 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -17,9 +17,7 @@ using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; -using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Menu; using osu.Game.Skinning; @@ -58,15 +56,13 @@ namespace osu.Game.Tests.Visual Dependencies.CacheAs(testBeatmapManager = new TestBeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); } - protected virtual bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true - && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; - public override void SetUpSteps() { base.SetUpSteps(); AddStep("load editor", LoadEditor); - AddUntilStep("wait for editor to load", () => EditorComponentsReady); + AddUntilStep("wait for editor to load", () => Editor?.ReadyForUse == true); + AddUntilStep("wait for beatmap updated", () => !Beatmap.IsDefault); } protected virtual void LoadEditor() From 13dcaf82ada9d3e1a96322c49a194da0652b525d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 16:50:07 +0900 Subject: [PATCH 173/363] Fix chat tests failing 1/10000 runs https://github.com/ppy/osu/blob/31a447fda0532fcf15f8c8251ca890c533386492/osu.Game/Online/Chat/ChannelManager.cs#L412-L414 Sigh. --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 4 ++-- osu.Game/Online/API/DummyAPIAccess.cs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 1fb0195368..73bad6e631 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -549,7 +549,7 @@ namespace osu.Game.Tests.Visual.Online private Channel createPrivateChannel() { - int id = RNG.Next(0, 10000); + int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1); return new Channel(new APIUser { Id = id, @@ -559,7 +559,7 @@ namespace osu.Game.Tests.Visual.Online private Channel createAnnounceChannel() { - int id = RNG.Next(0, 10000); + int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1); return new Channel { Name = $"Announce {id}", diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 245760a00a..07d544260e 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -15,10 +15,12 @@ namespace osu.Game.Online.API { public class DummyAPIAccess : Component, IAPIProvider { + public const int DUMMY_USER_ID = 1001; + public Bindable LocalUser { get; } = new Bindable(new APIUser { Username = @"Dummy", - Id = 1001, + Id = DUMMY_USER_ID, }); public BindableList Friends { get; } = new BindableList(); From 8c3c1f095eaf966c3cc08691531e78582ae659ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 17:04:28 +0900 Subject: [PATCH 174/363] Update rollback test expectations I can't find a better way to do this. It's very hard to trigger an actual failure in the import process these days. For now I've just made this work with the new assumptions. May be worth removing the test in the future if this ever backfires. --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index be11476c73..8c28f8cedd 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -607,6 +608,12 @@ namespace osu.Game.Tests.Database using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew)) using (var zip = ZipArchive.Open(brokenOsz)) { + foreach (var entry in zip.Entries.ToArray()) + { + if (entry.Key.EndsWith(".osu", StringComparison.InvariantCulture)) + zip.RemoveEntry(entry); + } + zip.AddEntry("broken.osu", brokenOsu, false); zip.SaveTo(outStream, CompressionType.Deflate); } @@ -627,7 +634,7 @@ namespace osu.Game.Tests.Database checkSingleReferencedFileCount(realm.Realm, 18); - Assert.AreEqual(1, loggedExceptionCount); + Assert.AreEqual(0, loggedExceptionCount); File.Delete(brokenTempFilename); }); From 401d9c1baed7bc9d60a88d286566a2e8537c1aff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 17:13:46 +0900 Subject: [PATCH 175/363] Fix `TestStoryboardSkipOutro` occasionally failing due to strict timings --- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 002e35f742..0ba8583b56 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -52,10 +52,11 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardSkipOutro() { + AddStep("set storyboard duration to long", () => currentStoryboardDuration = 200000); CreateTest(null); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); - AddAssert("player is no longer current screen", () => !Player.IsCurrentScreen()); + AddUntilStep("player is no longer current screen", () => !Player.IsCurrentScreen()); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } From f6a61472c413357bd46add03f2924d0662929dfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 17:59:40 +0900 Subject: [PATCH 176/363] Fix occasional failure in realm test proceedings due to incorrect `Debug.Assert` After a `BlockAllOperations`, the restoration of the `updateRealm` instance is not instance. It is posted to a `SynchronizationContext`. The assertion which has been removed in this commit was assuming it would always be an immediate operation. To ensure this works as expected, I've tracked the initialised state via a new `bool`. ```csharp System.TimeoutException : Attempting to block for migration took too long. 1) Host threw exception System.AggregateException: One or more errors occurred. (: ) ---> NUnit.Framework.AssertionException: : at osu.Framework.Logging.ThrowingTraceListener.Fail(String message1, String message2) at System.Diagnostics.TraceInternal.Fail(String message, String detailMessage) at System.Diagnostics.TraceInternal.TraceProvider.Fail(String message, String detailMessage) at System.Diagnostics.Debug.Fail(String message, String detailMessage) at osu.Game.Database.RealmAccess.BlockAllOperations() in /opt/buildagent/work/ecd860037212ac52/osu.Game/Database/RealmAccess.cs:line 813 at osu.Game.OsuGameBase.<>c__DisplayClass108_1.b__0() in /opt/buildagent/work/ecd860037212ac52/osu.Game/OsuGameBase.cs:line 449 at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() at osu.Framework.Threading.Scheduler.Update() at osu.Framework.Graphics.Drawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() ``` https://teamcity.ppy.sh/buildConfiguration/Osu_Build/322?hideProblemsFromDependencies=false&hideTestsFromDependencies=false&expandBuildTestsSection=true --- osu.Game/Database/RealmAccess.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 3ea7a14826..089a783177 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -102,6 +102,12 @@ namespace osu.Game.Database private Realm? updateRealm; + /// + /// Tracks whether a realm was ever fetched from this instance. + /// After a fetch occurs, blocking operations will be guaranteed to restore any subscriptions. + /// + private bool hasInitialisedOnce; + private bool isSendingNotificationResetEvents; public Realm Realm => ensureUpdateRealm(); @@ -121,6 +127,7 @@ namespace osu.Game.Database if (updateRealm == null) { updateRealm = getRealmInstance(); + hasInitialisedOnce = true; Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}"); @@ -806,13 +813,7 @@ namespace osu.Game.Database lock (realmLock) { - if (updateRealm == null) - { - // null realm means the update thread has not yet retrieved its instance. - // we don't need to worry about reviving the update instance in this case, so don't bother with the SynchronizationContext. - Debug.Assert(!ThreadSafety.IsUpdateThread); - } - else + if (hasInitialisedOnce) { if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); @@ -827,12 +828,12 @@ namespace osu.Game.Database action.Value?.Dispose(); customSubscriptionsResetMap[action.Key] = null; } + + updateRealm?.Dispose(); + updateRealm = null; } Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); - - updateRealm?.Dispose(); - updateRealm = null; } const int sleep_length = 200; From 5af1106fa5f0e9f49cadbbca052c3ee5d65d2df5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 18:04:52 +0900 Subject: [PATCH 177/363] Remove unused using statement --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 8c28f8cedd..6f5a79c9da 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; From 0ba29b6fa64eac5a908903fbbfbe0270662879a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 18:28:00 +0900 Subject: [PATCH 178/363] Use `currentScreen` instead to make sure the screen we care about is loaded --- osu.Game/Screens/Edit/Editor.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9dbb5f4cfe..81ae08aea1 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -111,13 +111,11 @@ namespace osu.Game.Screens.Edit if (!workingBeatmapUpdated) return false; - var loadedScreen = screenContainer?.Children.SingleOrDefault(s => s.IsLoaded); - - if (loadedScreen == null) + if (currentScreen?.IsLoaded != true) return false; - if (loadedScreen is EditorScreenWithTimeline) - return loadedScreen.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + if (currentScreen is EditorScreenWithTimeline) + return currentScreen.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; return true; } From 50c1c066deff819c782f77e6bcd1814ed8b3b2a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 18:41:27 +0900 Subject: [PATCH 179/363] Add various logging of global `WorkingBeatmap` state changes --- osu.Game/OsuGame.cs | 6 ++++++ osu.Game/Overlays/MusicController.cs | 3 +++ osu.Game/Screens/Menu/MainMenu.cs | 3 +++ osu.Game/Screens/Select/SongSelect.cs | 7 +++++-- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f51a18b36f..1458af1ea1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -486,6 +486,7 @@ namespace osu.Game /// public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate difficultyCriteria = null) { + Logger.Log($"Beginning {nameof(PresentBeatmap)} with beatmap {beatmap}"); Live databasedSet = null; if (beatmap.OnlineID > 0) @@ -522,6 +523,7 @@ namespace osu.Game } else { + Logger.Log($"Completing {nameof(PresentBeatmap)} with beatmap {beatmap} ruleset {selection.Ruleset}"); Ruleset.Value = selection.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection); } @@ -534,6 +536,8 @@ namespace osu.Game /// public void PresentScore(IScoreInfo score, ScorePresentType presentType = ScorePresentType.Results) { + Logger.Log($"Beginning {nameof(PresentScore)} with score {score}"); + // The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database // to ensure all the required data for presenting a replay are present. ScoreInfo databasedScoreInfo = null; @@ -568,6 +572,8 @@ namespace osu.Game PerformFromScreen(screen => { + Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset} to match score"); + Ruleset.Value = databasedScore.ScoreInfo.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index bce7e9b797..1c2c9f2b48 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -14,6 +14,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Database; @@ -106,10 +107,12 @@ namespace osu.Game.Overlays if (beatmap.Disabled) return; + Logger.Log($"{nameof(MusicController)} skipping to next track to {nameof(EnsurePlayingSomething)}"); NextTrack(); } else if (!IsPlaying) { + Logger.Log($"{nameof(MusicController)} starting playback to {nameof(EnsurePlayingSomething)}"); Play(); } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 6a74a6bd6e..ba63902b46 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -302,6 +303,8 @@ namespace osu.Game.Screens.Menu public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset) { + Logger.Log($"{nameof(MainMenu)} completing {nameof(PresentBeatmap)} with beatmap {beatmap} ruleset {ruleset}"); + Beatmap.Value = beatmap; Ruleset.Value = ruleset; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 41fb55a856..b89acfa570 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -494,7 +494,7 @@ namespace osu.Game.Screens.Select // clear pending task immediately to track any potential nested debounce operation. selectionChangedDebounce = null; - Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ShortName ?? "null"}"); + Logger.Log($"Song select updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ShortName ?? "null"}"); if (transferRulesetValue()) { @@ -519,7 +519,7 @@ namespace osu.Game.Screens.Select // In these cases, the other component has already loaded the beatmap, so we don't need to do so again. if (!EqualityComparer.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo)) { - Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); + Logger.Log($"Song select changing beatmap from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap?.ToString() ?? "null"}\""); Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); } @@ -737,7 +737,10 @@ namespace osu.Game.Screens.Select bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track; if (!track.IsRunning && (music.UserPauseRequested != true || isNewTrack)) + { + Logger.Log($"Song select decided to {nameof(ensurePlayingSelected)}"); music.Play(true); + } lastTrack.SetTarget(track); } From 1fc4fa68204c5cb3e9b1a02509ab5afcebdc4638 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 19:20:15 +0900 Subject: [PATCH 180/363] Remove unnecessary `Task.Run` workaround in tests --- .../Database/RealmSubscriptionRegistrationTests.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index b8ce036da1..c74341b5c9 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -84,11 +83,7 @@ namespace osu.Game.Tests.Database realm.Run(r => r.Refresh()); - // Without forcing the write onto its own thread, realm will internally run the operation synchronously, which can cause a deadlock with `WaitSafely`. - Task.Run(async () => - { - await realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); - }).WaitSafely(); + realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())).WaitSafely(); realm.Run(r => r.Refresh()); From c39c99bd43ce32f1f58ac274703e9dd3f542bcf6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 19:20:28 +0900 Subject: [PATCH 181/363] Ensure all async writes are completed before realm is disposed --- osu.Game/Database/RealmAccess.cs | 42 +++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 089a783177..2618fe373e 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -388,26 +388,43 @@ namespace osu.Game.Database } } + private readonly CountdownEvent pendingAsyncWrites = new CountdownEvent(0); + /// /// Write changes to realm asynchronously, guaranteeing order of execution. /// /// The work to run. public Task WriteAsync(Action action) { - // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval. - // Adding a forced Task.Run resolves this. + // Required to ensure the write is tracked and accounted for before disposal. + // Can potentially be avoided if we have a need to do so in the future. + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(WriteAsync)} must be called from the update thread."); - return Task.Run(async () => + lock (realmLock) { - total_writes_async.Value++; + // CountdownEvent will fail if already at zero. + if (!pendingAsyncWrites.TryAddCount()) + pendingAsyncWrites.Reset(1); - // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking - // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync - // server, which we don't use. May want to report upstream or revisit in the future. - using (var realm = getRealmInstance()) - // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). - await realm.WriteAsync(() => action(realm)); - }); + // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval. + // Adding a forced Task.Run resolves this. + var writeTask = Task.Run(async () => + { + total_writes_async.Value++; + + // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking + // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync + // server, which we don't use. May want to report upstream or revisit in the future. + using (var realm = getRealmInstance()) + // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). + await realm.WriteAsync(() => action(realm)); + + pendingAsyncWrites.Signal(); + }); + + return writeTask; + } } /// @@ -910,6 +927,9 @@ namespace osu.Game.Database public void Dispose() { + if (!pendingAsyncWrites.Wait(10000)) + Logger.Log("Realm took too long waiting on pending async writes", level: LogLevel.Error); + lock (realmLock) { updateRealm?.Dispose(); From 83982d258d96590722da57126219dfc1c920a7fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 19:34:28 +0900 Subject: [PATCH 182/363] Throw immediately if attempting to `WriteAsync` after disposed --- osu.Game/Database/RealmAccess.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2618fe373e..7a9561fc5e 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -396,6 +396,9 @@ namespace osu.Game.Database /// The work to run. public Task WriteAsync(Action action) { + if (isDisposed) + throw new ObjectDisposedException(nameof(RealmAccess)); + // Required to ensure the write is tracked and accounted for before disposal. // Can potentially be avoided if we have a need to do so in the future. if (!ThreadSafety.IsUpdateThread) From 62038850406fe5cccd1151cca141c1adb802ef1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 19:34:42 +0900 Subject: [PATCH 183/363] Add test coverage of realm async writes --- osu.Game.Tests/Database/GeneralUsageTests.cs | 61 ++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 65f805bafb..db6e97dab2 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -2,11 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Database { @@ -33,6 +36,64 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestAsyncWriteAsync() + { + RunTestWithRealmAsync(async (realm, _) => + { + await realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); + + realm.Run(r => r.Refresh()); + + Assert.That(realm.Run(r => r.All().Count()), Is.EqualTo(1)); + }); + } + + [Test] + public void TestAsyncWrite() + { + RunTestWithRealm((realm, _) => + { + realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())).WaitSafely(); + + realm.Run(r => r.Refresh()); + + Assert.That(realm.Run(r => r.All().Count()), Is.EqualTo(1)); + }); + } + + [Test] + public void TestAsyncWriteAfterDisposal() + { + RunTestWithRealm((realm, _) => + { + realm.Dispose(); + Assert.ThrowsAsync(() => realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo()))); + }); + } + + [Test] + public void TestAsyncWriteBeforeDisposal() + { + ManualResetEventSlim resetEvent = new ManualResetEventSlim(); + + RunTestWithRealm((realm, _) => + { + var writeTask = realm.WriteAsync(r => + { + // ensure that disposal blocks for our execution + Assert.That(resetEvent.Wait(100), Is.False); + + r.Add(TestResources.CreateTestBeatmapSetInfo()); + }); + + realm.Dispose(); + resetEvent.Set(); + + writeTask.WaitSafely(); + }); + } + /// /// Test to ensure that a `CreateContext` call nested inside a subscription doesn't cause any deadlocks /// due to context fetching semaphores. From aa7d54f8b6c31fd7e485721da2de000b4855414e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Jun 2022 20:10:15 +0900 Subject: [PATCH 184/363] Add logging for various cases of `SongSelect.FinaliseSelection` being aborted --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 4 ++++ osu.Game/Screens/Select/SongSelect.cs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 0f589a8b35..dbd679104e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; @@ -78,7 +79,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override bool SelectItem(PlaylistItem item) { if (operationInProgress.Value) + { + Logger.Log($"{nameof(SelectedItem)} aborted due to {nameof(operationInProgress)}"); return false; + } // If the client is already in a room, update via the client. // Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation. diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 41fb55a856..4e45abab3f 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -360,7 +360,10 @@ namespace osu.Game.Screens.Select { // This is very important as we have not yet bound to screen-level bindables before the carousel load is completed. if (!Carousel.BeatmapSetsLoaded) + { + Logger.Log($"{nameof(FinaliseSelection)} aborted as carousel beatmaps are not yet loaded"); return; + } if (ruleset != null) Ruleset.Value = ruleset; From 6c6de9ce533d5cd99b38390b396cabdc3ec34e9f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 27 Jun 2022 20:40:02 +0900 Subject: [PATCH 185/363] Fix typo --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 1c2c9f2b48..4a10f30a7a 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -107,7 +107,7 @@ namespace osu.Game.Overlays if (beatmap.Disabled) return; - Logger.Log($"{nameof(MusicController)} skipping to next track to {nameof(EnsurePlayingSomething)}"); + Logger.Log($"{nameof(MusicController)} skipping next track to {nameof(EnsurePlayingSomething)}"); NextTrack(); } else if (!IsPlaying) From 3e3843dcf9a8a943f730a3e74ca2cd5f44dd20c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 00:32:36 +0900 Subject: [PATCH 186/363] Fix potential crash on `CloseAllOverlays` due to collection mutation --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7f4e4af455..01f0b09a3e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -249,7 +249,7 @@ namespace osu.Game /// Whether the toolbar should also be hidden. public void CloseAllOverlays(bool hideToolbar = true) { - foreach (var overlay in focusedOverlays) + foreach (var overlay in focusedOverlays.ToArray()) overlay.Hide(); if (hideToolbar) Toolbar.Hide(); From b28cfc222b22a417f69f6449f36465c5a20cb385 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 01:19:20 +0900 Subject: [PATCH 187/363] Schedule unregister instead --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 01f0b09a3e..ef42522f9d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -231,7 +231,7 @@ namespace osu.Game /// /// Unregisters a blocking that was not created by itself. /// - private void unregisterBlockingOverlay(OverlayContainer overlayContainer) + private void unregisterBlockingOverlay(OverlayContainer overlayContainer) => Schedule(() => { externalOverlays.Remove(overlayContainer); @@ -239,7 +239,7 @@ namespace osu.Game focusedOverlays.Remove(focusedOverlayContainer); overlayContainer.Expire(); - } + }); #endregion @@ -249,7 +249,7 @@ namespace osu.Game /// Whether the toolbar should also be hidden. public void CloseAllOverlays(bool hideToolbar = true) { - foreach (var overlay in focusedOverlays.ToArray()) + foreach (var overlay in focusedOverlays) overlay.Hide(); if (hideToolbar) Toolbar.Hide(); From 569fde4b47976898cf1df4cd5965e54af7caa238 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 01:34:24 +0900 Subject: [PATCH 188/363] Add messages to all `InvalidOperationException`s Without this, they can be very non-descript and hard to track down --- osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs | 8 ++++---- osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs | 8 ++++---- .../Visual/SongSelect/TestSceneAdvancedStats.cs | 2 +- osu.Game/Online/API/Requests/Responses/APIScore.cs | 2 +- osu.Game/Online/Rooms/MultiplayerScore.cs | 2 +- .../Profile/Sections/Ranks/DrawableProfileScore.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs index a779fae510..14da07bc2d 100644 --- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs +++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs @@ -83,14 +83,14 @@ namespace osu.Game.Tests.NonVisual public override event Action NewResult { - add => throw new InvalidOperationException(); - remove => throw new InvalidOperationException(); + add => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); } public override event Action RevertResult { - add => throw new InvalidOperationException(); - remove => throw new InvalidOperationException(); + add => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); } public override Playfield Playfield { get; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 70d7f6a28b..707f807e64 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -273,14 +273,14 @@ namespace osu.Game.Tests.Visual.Gameplay public override event Action NewResult { - add => throw new InvalidOperationException(); - remove => throw new InvalidOperationException(); + add => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); } public override event Action RevertResult { - add => throw new InvalidOperationException(); - remove => throw new InvalidOperationException(); + add => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); } public override Playfield Playfield { get; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 77670c38f3..4510fda11d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("set beatmap", () => advancedStats.BeatmapInfo = new BeatmapInfo { - Ruleset = rulesets.GetRuleset(3) ?? throw new InvalidOperationException(), + Ruleset = rulesets.GetRuleset(3) ?? throw new InvalidOperationException("osu!mania ruleset not found"), Difficulty = new BeatmapDifficulty { CircleSize = 5, diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs index 1b56362aec..f236607761 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScore.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScore.cs @@ -88,7 +88,7 @@ namespace osu.Game.Online.API.Requests.Responses /// public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) { - var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException(); + var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally"); var rulesetInstance = ruleset.CreateInstance(); diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index 13f14bca5b..6f597e5b10 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -79,7 +79,7 @@ namespace osu.Game.Online.Rooms TotalScore = TotalScore, MaxCombo = MaxCombo, BeatmapInfo = beatmap, - Ruleset = rulesets.GetRuleset(playlistItem.RulesetID) ?? throw new InvalidOperationException(), + Ruleset = rulesets.GetRuleset(playlistItem.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {playlistItem.RulesetID} not found locally"), Statistics = Statistics, User = User, Accuracy = Accuracy, diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 54a262f6a6..90a357a281 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -136,7 +136,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(2), Children = Score.Mods.Select(mod => { - var ruleset = rulesets.GetRuleset(Score.RulesetID) ?? throw new InvalidOperationException(); + var ruleset = rulesets.GetRuleset(Score.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {Score.RulesetID} not found locally"); return new ModIcon(ruleset.CreateInstance().CreateModFromAcronym(mod.Acronym)) { diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 0a6f9974b0..96425e8bc8 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Edit public BeatmapInfo BeatmapInfo { get => beatmapInfo; - set => throw new InvalidOperationException(); + set => throw new InvalidOperationException($"Can't set {nameof(BeatmapInfo)} on {nameof(EditorBeatmap)}"); } public BeatmapMetadata Metadata => beatmapInfo.Metadata; diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index fc29c5aac5..db1d94aee2 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Spectator FrameSendAttempts++; if (ShouldFailSendingFrames) - return Task.FromException(new InvalidOperationException()); + return Task.FromException(new InvalidOperationException($"Intentional fail via {ShouldFailSendingFrames}")); return ((ISpectatorClient)this).UserSentFrames(api.LocalUser.Value.Id, bundle); } From 54fe6b7df3ae62e0dc7a61f28ef0e3ff85aee06e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 01:43:09 +0900 Subject: [PATCH 189/363] Fix incorrect string interpolation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index db1d94aee2..2531f3c485 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Spectator FrameSendAttempts++; if (ShouldFailSendingFrames) - return Task.FromException(new InvalidOperationException($"Intentional fail via {ShouldFailSendingFrames}")); + return Task.FromException(new InvalidOperationException($"Intentional fail via {nameof(ShouldFailSendingFrames)}")); return ((ISpectatorClient)this).UserSentFrames(api.LocalUser.Value.Id, bundle); } From 1a0228415d526599dbba42ba8cc08ba767142367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Jun 2022 22:26:00 +0200 Subject: [PATCH 190/363] Wait for and dismiss notification in editor navigation test --- osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs index 62ca97d5e0..a21ef6f897 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; @@ -35,6 +36,8 @@ namespace osu.Game.Tests.Visual.Editing () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.IsLoaded); + AddUntilStep("wait for completion notification", () => Game.Notifications.ChildrenOfType().Count() == 1); + AddStep("dismiss notifications", () => Game.Notifications.Hide()); AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); From c1075d113fb13b586a216444946057f936c8d86a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 13:58:35 +0900 Subject: [PATCH 191/363] Add logging around current channel changes and join requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tracking down a flaky test (https://teamcity.ppy.sh/buildConfiguration/Osu_Build/553?hideProblemsFromDependencies=false&expandBuildTestsSection=true&hideTestsFromDependencies=false): ```csharp TearDown : System.TimeoutException : "PM Channel 1 displayed" timed out --TearDown at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) --- End of stack trace from previous location --- at osu.Framework.Testing.TestSceneTestRunner.TestRunner.RunTestBlocking(TestScene test) at osu.Game.Tests.Visual.OsuTestScene.OsuTestSceneTestRunner.RunTestBlocking(TestScene test) in /opt/buildagent/work/ecd860037212ac52/osu.Game/Tests/Visual/OsuTestScene.cs:line 503 at osu.Framework.Testing.TestScene.RunTestsFromNUnit() ------- Stdout: ------- [runtime] 2022-06-27 23:18:55 [verbose]: 💨 Class: TestSceneChatOverlay [runtime] 2022-06-27 23:18:55 [verbose]: 🔶 Test: TestKeyboardNextChannel [runtime] 2022-06-27 23:18:55 [verbose]: Chat is now polling every 60000 ms [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #1 Setup request handler [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #2 Add test channels [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #3 Show overlay with channels [runtime] 2022-06-27 23:18:55 [verbose]: Unhandled Request Type: osu.Game.Online.API.Requests.CreateChannelRequest [network] 2022-06-27 23:18:55 [verbose]: Failing request osu.Game.Online.API.Requests.CreateChannelRequest (System.InvalidOperationException: DummyAPIAccess cannot process this request.) [runtime] 2022-06-27 23:18:55 [verbose]: Unhandled Request Type: osu.Game.Online.API.Requests.CreateChannelRequest [network] 2022-06-27 23:18:55 [verbose]: Failing request osu.Game.Online.API.Requests.CreateChannelRequest (System.InvalidOperationException: DummyAPIAccess cannot process this request.) [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #4 Select channel 1 [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #5 Channel 1 is visible [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #6 Press document next keys [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #7 Channel 2 is visible [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #8 Press document next keys [runtime] 2022-06-27 23:18:55 [verbose]: 🔸 Step #9 PM Channel 1 displayed [network] 2022-06-27 23:18:55 [verbose]: Request to https://a.ppy.sh/587 failed with System.Net.WebException: NotFound. [network] 2022-06-27 23:18:55 [verbose]: Request to https://a.ppy.sh/503 failed with System.Net.WebException: NotFound. [runtime] 2022-06-27 23:19:05 [verbose]: 💥 Failed (on attempt 5,550) [runtime] 2022-06-27 23:19:05 [verbose]: ⏳ Currently loading components (0) [runtime] 2022-06-27 23:19:05 [verbose]: 🧵 Task schedulers [runtime] 2022-06-27 23:19:05 [verbose]: LoadComponentsAsync (standard) concurrency:4 running:0 pending:0 [runtime] 2022-06-27 23:19:05 [verbose]: LoadComponentsAsync (long load) concurrency:4 running:0 pending:0 [runtime] 2022-06-27 23:19:05 [verbose]: 🎱 Thread pool [runtime] 2022-06-27 23:19:05 [verbose]: worker: min 1 max 32,767 available 32,766 [runtime] 2022-06-27 23:19:05 [verbose]: completion: min 1 max 1,000 available 1,000 [runtime] 2022-06-27 23:19:05 [debug]: Focus on "ChatTextBox" no longer valid as a result of unfocusIfNoLongerValid. [runtime] 2022-06-27 23:19:05 [debug]: Focus changed from ChatTextBox to nothing. ``` This kind of logging should be helpful: ```csharp [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #5 Channel 1 is visible [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #6 Press document next keys [runtime] 2022-06-28 04:59:57 [verbose]: Current channel changed to #channel-2 [runtime] 2022-06-28 04:59:57 [debug]: Pressed (DocumentNext) handled by TestSceneChatOverlay+TestChatOverlay. [runtime] 2022-06-28 04:59:57 [debug]: KeyDownEvent(PageDown, False) handled by ManualInputManager+LocalPlatformActionContainer. [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #7 Channel 2 is visible [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #8 Press document next keys [runtime] 2022-06-28 04:59:57 [verbose]: Current channel changed to test user 685 [runtime] 2022-06-28 04:59:57 [debug]: Pressed (DocumentNext) handled by TestSceneChatOverlay+TestChatOverlay. [runtime] 2022-06-28 04:59:57 [debug]: KeyDownEvent(PageDown, False) handled by ManualInputManager+LocalPlatformActionContainer. [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #9 PM Channel 1 displayed [runtime] 2022-06-28 04:59:57 [verbose]: 🔸 Step #10 Press document next keys [runtime] 2022-06-28 04:59:57 [verbose]: Current channel changed to test user 218 ``` --- .../Visual/Online/TestSceneChatOverlay.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 73bad6e631..ff2817c439 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -472,8 +472,8 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); - waitForChannel1Visible(); + AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); waitForChannel2Visible(); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 48dfaadfa5..ec84b0643d 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -133,12 +133,14 @@ namespace osu.Game.Online.Chat ?? JoinChannel(new Channel(user)); } - private void currentChannelChanged(ValueChangedEvent e) + private void currentChannelChanged(ValueChangedEvent channel) { - bool isSelectorChannel = e.NewValue is ChannelListing.ChannelListingChannel; + bool isSelectorChannel = channel.NewValue is ChannelListing.ChannelListingChannel; if (!isSelectorChannel) - JoinChannel(e.NewValue); + JoinChannel(channel.NewValue); + + Logger.Log($"Current channel changed to {channel.NewValue}"); } /// @@ -447,9 +449,17 @@ namespace osu.Game.Online.Chat return channel; case ChannelType.PM: + Logger.Log($"Attempting to join PM channel {channel}"); + var createRequest = new CreateChannelRequest(channel); + createRequest.Failure += e => + { + Logger.Log($"Failed to join PM channel {channel} ({e.Message})"); + }; createRequest.Success += resChannel => { + Logger.Log($"Joined PM channel {channel} ({resChannel.ChannelID})"); + if (resChannel.ChannelID.HasValue) { channel.Id = resChannel.ChannelID.Value; @@ -463,9 +473,19 @@ namespace osu.Game.Online.Chat break; default: + Logger.Log($"Attempting to join public channel {channel}"); + var req = new JoinChannelRequest(channel); - req.Success += () => joinChannel(channel, fetchInitialMessages); - req.Failure += _ => LeaveChannel(channel); + req.Success += () => + { + Logger.Log($"Joined public channel {channel}"); + joinChannel(channel, fetchInitialMessages); + }; + req.Failure += e => + { + Logger.Log($"Failed to join public channel {channel} ({e.Message})"); + LeaveChannel(channel); + }; api.Queue(req); return channel; } From a2701a32055c752fb2ce6c8a937623b69da583b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 14:58:33 +0900 Subject: [PATCH 192/363] Increase leaniences on `TestSceneSpectatorPlayback.TestWithSendFailure` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not really sure how to improve this further, but should help with cases like this: ```csharp [runtime] 2022-06-28 05:32:06 [verbose]: 💨 Class: TestSceneSpectatorPlayback [runtime] 2022-06-28 05:32:06 [verbose]: 🔶 Test: TestWithSendFailure [runtime] 2022-06-28 05:32:06 [verbose]: 🔸 Step #1 Setup containers [runtime] 2022-06-28 05:32:06 [verbose]: Received 1 new frames (total 1 of 2) [runtime] 2022-06-28 05:32:06 [verbose]: 🔸 Step #2 received frames [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 7 of 8) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 13 of 19) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 19 of 29) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 25 of 44) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 31 of 45) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 37 of 59) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 43 of 67) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 49 of 125) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 55 of 126) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 61 of 127) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 67 of 128) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 73 of 129) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 79 of 130) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 85 of 131) [runtime] 2022-06-28 05:32:06 [verbose]: ✔️ 22 repetitions [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 91 of 132) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 97 of 133) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 103 of 134) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 109 of 135) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 115 of 136) [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 121 of 137) [runtime] 2022-06-28 05:32:06 [verbose]: 🔸 Step #3 start failing sends [runtime] 2022-06-28 05:32:06 [verbose]: Received 6 new frames (total 127 of 138) [runtime] 2022-06-28 05:32:06 [verbose]: 🔸 Step #4 wait for send attempts [runtime] 2022-06-28 05:32:06 [verbose]: 🔸 Step #5 frames did not increase [runtime] 2022-06-28 05:32:06 [verbose]: 💥 Failed [runtime] 2022-06-28 05:32:06 [verbose]: ⏳ Currently loading components (0) [runtime] 2022-06-28 05:32:06 [verbose]: 🧵 Task schedulers [runtime] 2022-06-28 05:32:06 [verbose]: LoadComponentsAsync (standard) concurrency:4 running:0 pending:0 [runtime] 2022-06-28 05:32:06 [verbose]: LoadComponentsAsync (long load) concurrency:4 running:0 pending:0 [runtime] 2022-06-28 05:32:06 [verbose]: 🎱 Thread pool [runtime] 2022-06-28 05:32:06 [verbose]: worker: min 1 max 32,767 available 32,766 [runtime] 2022-06-28 05:32:06 [verbose]: completion: min 1 max 1,000 available 1,000 [runtime] 2022-06-28 05:32:06 [verbose]: Host execution state changed to Stopping ``` https://teamcity.ppy.sh/buildConfiguration/Osu_Build/811?hideProblemsFromDependencies=false&hideTestsFromDependencies=false&expandBuildTestsSection=true --- .../Visual/Gameplay/TestSceneSpectatorPlayback.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 1e517efef2..5fad661e9b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -167,11 +167,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("start failing sends", () => { spectatorClient.ShouldFailSendingFrames = true; - framesReceivedSoFar = replay.Frames.Count; frameSendAttemptsSoFar = spectatorClient.FrameSendAttempts; }); - AddUntilStep("wait for send attempts", () => spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 5); + AddUntilStep("wait for next send attempt", () => + { + framesReceivedSoFar = replay.Frames.Count; + return spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 1; + }); + + AddUntilStep("wait for more send attempts", () => spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 10); AddAssert("frames did not increase", () => framesReceivedSoFar == replay.Frames.Count); AddStep("stop failing sends", () => spectatorClient.ShouldFailSendingFrames = false); From 22b254e5c5ce9aee62f21f0863394f26e1212a2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 15:09:28 +0900 Subject: [PATCH 193/363] Handle task exception outside of schedule to avoid unobserved exceptions --- osu.Game/Online/Spectator/SpectatorClient.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 4a43aa6c66..08e1e78d86 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -294,17 +294,21 @@ namespace osu.Game.Online.Spectator lastSend = tcs.Task; - SendFramesInternal(bundle).ContinueWith(t => Schedule(() => + SendFramesInternal(bundle).ContinueWith(t => { + // Handle exception outside of `Schedule` to ensure it doesn't go unovserved. bool wasSuccessful = t.Exception == null; - // If the last bundle send wasn't successful, try again without dequeuing. - if (wasSuccessful) - pendingFrameBundles.Dequeue(); + return Schedule(() => + { + // If the last bundle send wasn't successful, try again without dequeuing. + if (wasSuccessful) + pendingFrameBundles.Dequeue(); - tcs.SetResult(wasSuccessful); - sendNextBundleIfRequired(); - })); + tcs.SetResult(wasSuccessful); + sendNextBundleIfRequired(); + }); + }); } } } From 35745c83b7fa468468c5b032bda97d5120c59f27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 15:19:02 +0900 Subject: [PATCH 194/363] Replace dodgy `SetUpSteps` overriding with usage of `HasCustomSteps` --- .../TestSceneCatchPlayerLegacySkin.cs | 2 +- .../TestSceneSliderSnaking.cs | 29 ++++++------------- .../TestSceneStoryboardSamplePlayback.cs | 2 +- .../Gameplay/TestSceneStoryboardWithOutro.cs | 12 ++++---- osu.Game/Tests/Visual/PlayerTestScene.cs | 4 +-- 5 files changed, 19 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index 731cb4e135..8dd6f82c57 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("change component scale", () => Player.ChildrenOfType().First().Scale = new Vector2(2f)); AddStep("update target", () => Player.ChildrenOfType().ForEach(LegacySkin.UpdateDrawableTarget)); AddStep("exit player", () => Player.Exit()); - CreateTest(null); + CreateTest(); } AddAssert("legacy HUD combo counter hidden", () => diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 5706955fc5..366793058d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -66,10 +66,7 @@ namespace osu.Game.Rulesets.Osu.Tests drawableSlider = null; }); - [SetUpSteps] - public override void SetUpSteps() - { - } + protected override bool HasCustomSteps => true; [TestCase(0)] [TestCase(1)] @@ -77,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestSnakingEnabled(int sliderIndex) { AddStep("enable autoplay", () => autoplay = true); - base.SetUpSteps(); + CreateTest(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); retrieveSlider(sliderIndex); @@ -101,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestSnakingDisabled(int sliderIndex) { AddStep("have autoplay", () => autoplay = true); - base.SetUpSteps(); + CreateTest(); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); retrieveSlider(sliderIndex); @@ -121,8 +118,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("enable autoplay", () => autoplay = true); setSnaking(true); - base.SetUpSteps(); - + CreateTest(); // repeat might have a chance to update its position depending on where in the frame its hit, // so some leniency is allowed here instead of checking strict equality addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionAlmostSame); @@ -133,15 +129,14 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("disable autoplay", () => autoplay = false); setSnaking(true); - base.SetUpSteps(); - + CreateTest(); addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionDecreased); } private void retrieveSlider(int index) { AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]); - addSeekStep(() => slider); + addSeekStep(() => slider.StartTime); AddUntilStep("retrieve drawable slider", () => (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null); } @@ -205,16 +200,10 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - private void addSeekStep(Func slider) + private void addSeekStep(Func getTime) { - AddStep("seek to slider", () => Player.GameplayClockContainer.Seek(slider().StartTime)); - AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(slider().StartTime, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); - } - - private void addSeekStep(Func time) - { - AddStep("seek to time", () => Player.GameplayClockContainer.Seek(time())); - AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + AddStep("seek to time", () => Player.GameplayClockContainer.Seek(getTime())); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(getTime(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 079d459beb..f0e184d727 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void createPlayerTest() { - CreateTest(null); + CreateTest(); AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null); waitUntilStoryboardSamplesPlay(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs index 0ba8583b56..e2b2ad85a3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestStoryboardSkipOutro() { AddStep("set storyboard duration to long", () => currentStoryboardDuration = 200000); - CreateTest(null); + CreateTest(); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); AddUntilStep("player is no longer current screen", () => !Player.IsCurrentScreen()); @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardNoSkipOutro() { - CreateTest(null); + CreateTest(); AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration); AddUntilStep("wait for score shown", () => Player.IsScoreShown); } @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardExitDuringOutroStillExits() { - CreateTest(null); + CreateTest(); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("exit via pause", () => Player.ExitViaPause()); AddAssert("player exited", () => !Player.IsCurrentScreen() && Player.GetChildScreen() == null); @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(true)] public void TestStoryboardToggle(bool enabledAtBeginning) { - CreateTest(null); + CreateTest(); AddStep($"{(enabledAtBeginning ? "enable" : "disable")} storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, enabledAtBeginning)); AddStep("toggle storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, !enabledAtBeginning)); AddUntilStep("wait for score shown", () => Player.IsScoreShown); @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay { SkipOverlay.FadeContainer fadeContainer() => Player.ChildrenOfType().First(); - CreateTest(null); + CreateTest(); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPerformExitNoOutro() { - CreateTest(null); + CreateTest(); AddStep("disable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, false)); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddStep("exit via pause", () => Player.ExitViaPause()); diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 521fd8f21a..e1714299b6 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -39,10 +39,10 @@ namespace osu.Game.Tests.Visual base.SetUpSteps(); if (!HasCustomSteps) - CreateTest(null); + CreateTest(); } - protected void CreateTest(Action action) + protected void CreateTest([CanBeNull] Action action = null) { if (action != null && !HasCustomSteps) throw new InvalidOperationException($"Cannot add custom test steps without {nameof(HasCustomSteps)} being set."); From 6bfd351dec8e4a1a38a8ab60e36580534b3fa6ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 15:23:29 +0900 Subject: [PATCH 195/363] Add logging of `GameplayClockContainer` seeks --- osu.Game/Screens/Play/GameplayClockContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6403943a53..9396b3311f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Timing; namespace osu.Game.Screens.Play @@ -101,6 +102,8 @@ namespace osu.Game.Screens.Play /// The destination time to seek to. public virtual void Seek(double time) { + Logger.Log($"{nameof(GameplayClockContainer)} seeking to {time}"); + AdjustableSource.Seek(time); // Manually process to make sure the gameplay clock is correctly updated after a seek. From e10ac45fd73b7345898c700591edabfe7d84ee3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 16:54:53 +0900 Subject: [PATCH 196/363] Remove probably redundant `realmLock` As far as I can tell all accesses are safe due to update thread guarantees. The only weird one may be async writes during a `BlockAllOperations`, but the `Compact` loop should handle this quite amicably. --- osu.Game/Database/RealmAccess.cs | 147 +++++++++++++------------------ 1 file changed, 62 insertions(+), 85 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 7a9561fc5e..715066f32d 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -98,8 +98,6 @@ namespace osu.Game.Database private static readonly GlobalStatistic total_writes_async = GlobalStatistics.Get(@"Realm", @"Writes (Async)"); - private readonly object realmLock = new object(); - private Realm? updateRealm; /// @@ -122,24 +120,21 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"Use {nameof(getRealmInstance)} when performing realm operations from a non-update thread"); - lock (realmLock) + if (updateRealm == null) { - if (updateRealm == null) - { - updateRealm = getRealmInstance(); - hasInitialisedOnce = true; + updateRealm = getRealmInstance(); + hasInitialisedOnce = true; - Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}"); + Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}"); - // Resubscribe any subscriptions - foreach (var action in customSubscriptionsResetMap.Keys) - registerSubscription(action); - } - - Debug.Assert(updateRealm != null); - - return updateRealm; + // Resubscribe any subscriptions + foreach (var action in customSubscriptionsResetMap.Keys) + registerSubscription(action); } + + Debug.Assert(updateRealm != null); + + return updateRealm; } internal static bool CurrentThreadSubscriptionsAllowed => current_thread_subscriptions_allowed.Value; @@ -404,30 +399,27 @@ namespace osu.Game.Database if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(WriteAsync)} must be called from the update thread."); - lock (realmLock) + // CountdownEvent will fail if already at zero. + if (!pendingAsyncWrites.TryAddCount()) + pendingAsyncWrites.Reset(1); + + // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval. + // Adding a forced Task.Run resolves this. + var writeTask = Task.Run(async () => { - // CountdownEvent will fail if already at zero. - if (!pendingAsyncWrites.TryAddCount()) - pendingAsyncWrites.Reset(1); + total_writes_async.Value++; - // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval. - // Adding a forced Task.Run resolves this. - var writeTask = Task.Run(async () => - { - total_writes_async.Value++; + // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking + // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync + // server, which we don't use. May want to report upstream or revisit in the future. + using (var realm = getRealmInstance()) + // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). + await realm.WriteAsync(() => action(realm)); - // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking - // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync - // server, which we don't use. May want to report upstream or revisit in the future. - using (var realm = getRealmInstance()) - // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). - await realm.WriteAsync(() => action(realm)); + pendingAsyncWrites.Signal(); + }); - pendingAsyncWrites.Signal(); - }); - - return writeTask; - } + return writeTask; } /// @@ -452,14 +444,11 @@ namespace osu.Game.Database public IDisposable RegisterForNotifications(Func> query, NotificationCallbackDelegate callback) where T : RealmObjectBase { - lock (realmLock) - { - Func action = realm => query(realm).QueryAsyncWithNotifications(callback); + Func action = realm => query(realm).QueryAsyncWithNotifications(callback); - // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); - return RegisterCustomSubscription(action); - } + // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. + notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); + return RegisterCustomSubscription(action); } /// @@ -550,15 +539,12 @@ namespace osu.Game.Database void unsubscribe() { - lock (realmLock) + if (customSubscriptionsResetMap.TryGetValue(action, out var unsubscriptionAction)) { - if (customSubscriptionsResetMap.TryGetValue(action, out var unsubscriptionAction)) - { - unsubscriptionAction?.Dispose(); - customSubscriptionsResetMap.Remove(action); - notificationsResetMap.Remove(action); - total_subscriptions.Value--; - } + unsubscriptionAction?.Dispose(); + customSubscriptionsResetMap.Remove(action); + notificationsResetMap.Remove(action); + total_subscriptions.Value--; } } }); @@ -568,19 +554,16 @@ namespace osu.Game.Database { Debug.Assert(ThreadSafety.IsUpdateThread); - lock (realmLock) - { - // Retrieve realm instance outside of flag update to ensure that the instance is retrieved, - // as attempting to access it inside the subscription if it's not constructed would lead to - // cyclic invocations of the subscription callback. - var realm = Realm; + // Retrieve realm instance outside of flag update to ensure that the instance is retrieved, + // as attempting to access it inside the subscription if it's not constructed would lead to + // cyclic invocations of the subscription callback. + var realm = Realm; - Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); + Debug.Assert(!customSubscriptionsResetMap.TryGetValue(action, out var found) || found == null); - current_thread_subscriptions_allowed.Value = true; - customSubscriptionsResetMap[action] = action(realm); - current_thread_subscriptions_allowed.Value = false; - } + current_thread_subscriptions_allowed.Value = true; + customSubscriptionsResetMap[action] = action(realm); + current_thread_subscriptions_allowed.Value = false; } private Realm getRealmInstance() @@ -831,31 +814,28 @@ namespace osu.Game.Database { realmRetrievalLock.Wait(); - lock (realmLock) + if (hasInitialisedOnce) { - if (hasInitialisedOnce) + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + + syncContext = SynchronizationContext.Current; + + // Before disposing the update context, clean up all subscriptions. + // Note that in the case of realm notification subscriptions, this is not really required (they will be cleaned up by disposal). + // In the case of custom subscriptions, we want them to fire before the update realm is disposed in case they do any follow-up work. + foreach (var action in customSubscriptionsResetMap.ToArray()) { - if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); - - syncContext = SynchronizationContext.Current; - - // Before disposing the update context, clean up all subscriptions. - // Note that in the case of realm notification subscriptions, this is not really required (they will be cleaned up by disposal). - // In the case of custom subscriptions, we want them to fire before the update realm is disposed in case they do any follow-up work. - foreach (var action in customSubscriptionsResetMap.ToArray()) - { - action.Value?.Dispose(); - customSubscriptionsResetMap[action.Key] = null; - } - - updateRealm?.Dispose(); - updateRealm = null; + action.Value?.Dispose(); + customSubscriptionsResetMap[action.Key] = null; } - Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + updateRealm?.Dispose(); + updateRealm = null; } + Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + const int sleep_length = 200; int timeout = 5000; @@ -933,10 +913,7 @@ namespace osu.Game.Database if (!pendingAsyncWrites.Wait(10000)) Logger.Log("Realm took too long waiting on pending async writes", level: LogLevel.Error); - lock (realmLock) - { - updateRealm?.Dispose(); - } + updateRealm?.Dispose(); if (!isDisposed) { From d64959ad0c8a7dccf241741801f5854586229edb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 17:07:49 +0900 Subject: [PATCH 197/363] Add test coverage of async writes during a blocking operation --- osu.Game.Tests/Database/GeneralUsageTests.cs | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index db6e97dab2..5b6f7a0a53 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -49,6 +49,27 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestAsyncWriteWhileBlocking() + { + RunTestWithRealm((realm, _) => + { + Task writeTask; + + using (realm.BlockAllOperations()) + { + writeTask = realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); + Thread.Sleep(100); + Assert.That(writeTask.IsCompleted, Is.False); + } + + writeTask.WaitSafely(); + + realm.Run(r => r.Refresh()); + Assert.That(realm.Run(r => r.All().Count()), Is.EqualTo(1)); + }); + } + [Test] public void TestAsyncWrite() { From 21d31ee218ab60d886c20155e9b817f5dc44257e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 18:04:08 +0900 Subject: [PATCH 198/363] Ensure joined channels are actually joined to avoid unexpected tab order --- .../Visual/Online/TestSceneChatOverlay.cs | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index ff2817c439..c57606d44b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -40,7 +40,6 @@ namespace osu.Game.Tests.Visual.Online private ChannelManager channelManager; private APIUser testUser; - private Channel testPMChannel; private Channel[] testChannels; private Channel testChannel1 => testChannels[0]; @@ -53,7 +52,6 @@ namespace osu.Game.Tests.Visual.Online public void SetUp() => Schedule(() => { testUser = new APIUser { Username = "test user", Id = 5071479 }; - testPMChannel = new Channel(testUser); testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray(); Child = new DependencyProvidingContainer @@ -181,7 +179,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Show overlay", () => chatOverlay.Show()); AddAssert("Listing is visible", () => listingIsVisible); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); waitForChannel1Visible(); } @@ -203,12 +201,11 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestChannelCloseButton() { + var testPMChannel = new Channel(testUser); + AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join PM and public channels", () => - { - channelManager.JoinChannel(testChannel1); - channelManager.JoinChannel(testPMChannel); - }); + joinTestChannel(0); + joinChannel(testPMChannel); AddStep("Select PM channel", () => clickDrawable(getChannelListItem(testPMChannel))); AddStep("Click close button", () => { @@ -229,7 +226,7 @@ namespace osu.Game.Tests.Visual.Online public void TestChatCommand() { AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddStep("Open chat with user", () => channelManager.PostCommand($"chat {testUser.Username}")); AddAssert("PM channel is selected", () => @@ -248,14 +245,16 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestMultiplayerChannelIsNotShown() { - Channel multiplayerChannel = null; + Channel multiplayerChannel; AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser()) + + joinChannel(multiplayerChannel = new Channel(new APIUser()) { Name = "#mp_1", Type = ChannelType.Multiplayer, - })); + }); + AddAssert("Channel is joined", () => channelManager.JoinedChannels.Contains(multiplayerChannel)); AddUntilStep("Channel not present in listing", () => !chatOverlay.ChildrenOfType() .Where(item => item.IsPresent) @@ -269,7 +268,7 @@ namespace osu.Game.Tests.Visual.Online Message message = null; AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddStep("Send message in channel 1", () => { @@ -291,8 +290,8 @@ namespace osu.Game.Tests.Visual.Online Message message = null; AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); - AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2)); + joinTestChannel(0); + joinTestChannel(1); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddStep("Send message in channel 2", () => { @@ -314,8 +313,8 @@ namespace osu.Game.Tests.Visual.Online Message message = null; AddStep("Show overlay", () => chatOverlay.Show()); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); - AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2)); + joinTestChannel(0); + joinTestChannel(1); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddStep("Send message in channel 2", () => { @@ -337,7 +336,7 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Send message in channel 1", () => { testChannel1.AddNewMessages(message = new Message @@ -357,7 +356,7 @@ namespace osu.Game.Tests.Visual.Online { Message message = null; - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Send message in channel 1", () => { testChannel1.AddNewMessages(message = new Message @@ -378,7 +377,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Show overlay", () => chatOverlay.Show()); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); waitForChannel1Visible(); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); @@ -404,11 +403,11 @@ namespace osu.Game.Tests.Visual.Online chatOverlay.Show(); chatOverlay.SlowLoading = true; }); - AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + joinTestChannel(0); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddUntilStep("Channel 1 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Loading); - AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2)); + joinTestChannel(1); AddStep("Select channel 2", () => clickDrawable(getChannelListItem(testChannel2))); AddUntilStep("Channel 2 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel2).LoadState == LoadState.Loading); @@ -461,15 +460,13 @@ namespace osu.Game.Tests.Visual.Online Channel pmChannel1 = createPrivateChannel(); Channel pmChannel2 = createPrivateChannel(); - AddStep("Show overlay with channels", () => - { - channelManager.JoinChannel(testChannel1); - channelManager.JoinChannel(testChannel2); - channelManager.JoinChannel(pmChannel1); - channelManager.JoinChannel(pmChannel2); - channelManager.JoinChannel(announceChannel); - chatOverlay.Show(); - }); + joinTestChannel(0); + joinTestChannel(1); + joinChannel(pmChannel1); + joinChannel(pmChannel2); + joinChannel(announceChannel); + + AddStep("Show overlay", () => chatOverlay.Show()); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); waitForChannel1Visible(); @@ -490,6 +487,18 @@ namespace osu.Game.Tests.Visual.Online waitForChannel1Visible(); } + private void joinTestChannel(int i) + { + AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i])); + AddUntilStep("wait for join completed", () => testChannels[i].Joined.Value); + } + + private void joinChannel(Channel channel) + { + AddStep($"Join channel {channel}", () => channelManager.JoinChannel(channel)); + AddUntilStep("wait for join completed", () => channel.Joined.Value); + } + private void waitForChannel1Visible() => AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel?.Channel == testChannel1); From ed1b809f54b22116ee6f04738f85d11a22ff8457 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 18:11:30 +0900 Subject: [PATCH 199/363] Add missing request handling to actually join PM channels --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index c57606d44b..d2d9b9a9e5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -78,6 +78,14 @@ namespace osu.Game.Tests.Visual.Online { switch (req) { + case CreateChannelRequest createRequest: + createRequest.TriggerSuccess(new APIChatChannel + { + ChannelID = ((int)createRequest.Channel.Id), + RecentMessages = new List() + }); + return true; + case GetUpdatesRequest getUpdates: getUpdates.TriggerFailure(new WebException()); return true; From 975ba83838a63f97e7942f3958ad9d3dd50d80d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 18:19:43 +0900 Subject: [PATCH 200/363] 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 8f4a102cce..f884ce0eff 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 b9033f1bb9..dbfdf0fde8 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 a1e4cf3ba0..11c177dbae 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From c282e193169431e8c7bbe1bf26ad77b6c197396b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 16:29:19 +0900 Subject: [PATCH 201/363] Update language initialisation in line with framework `LocalisationManager` changes --- osu.Game/OsuGame.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ef42522f9d..bd0a2680ae 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -24,6 +24,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; @@ -686,27 +687,29 @@ namespace osu.Game { base.LoadComplete(); - foreach (var language in Enum.GetValues(typeof(Language)).OfType()) + var languages = Enum.GetValues(typeof(Language)).OfType(); + + var mappings = languages.Select(language => { #if DEBUG if (language == Language.debug) - { - Localisation.AddLanguage(Language.debug.ToString(), new DebugLocalisationStore()); - continue; - } + return new LocaleMapping("debug", new DebugLocalisationStore()); #endif string cultureCode = language.ToCultureCode(); try { - Localisation.AddLanguage(cultureCode, new ResourceManagerLocalisationStore(cultureCode)); + return new LocaleMapping(new ResourceManagerLocalisationStore(cultureCode)); } catch (Exception ex) { Logger.Error(ex, $"Could not load localisations for language \"{cultureCode}\""); + return null; } - } + }).Where(m => m != null); + + Localisation.AddLocaleMappings(mappings); // The next time this is updated is in UpdateAfterChildren, which occurs too late and results // in the cursor being shown for a few frames during the intro. From 7c9c499e19d033ef0b352df7930668c67ddd3017 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 Jun 2022 19:21:41 +0900 Subject: [PATCH 202/363] Update resources (translation updates) --- 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 f884ce0eff..2ef3bb2dd1 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 dbfdf0fde8..ab690b22a6 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 11c177dbae..a84fc40a45 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 2269f1046ed34d4dad8542013a322290ebda5458 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Tue, 28 Jun 2022 20:59:03 +0800 Subject: [PATCH 203/363] Remove the nullable disable annotations. --- osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs | 2 -- osu.Game/Migrations/20171019041408_InitialCreate.cs | 2 -- .../Migrations/20171025071459_AddMissingIndexRules.Designer.cs | 2 -- osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs | 2 -- ...0171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs | 2 -- .../20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs | 2 -- .../20171209034410_AddRulesetInfoShortName.Designer.cs | 2 -- osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs | 2 -- osu.Game/Migrations/20180125143340_Settings.Designer.cs | 2 -- osu.Game/Migrations/20180125143340_Settings.cs | 2 -- osu.Game/Migrations/20180131154205_AddMuteBinding.cs | 2 -- osu.Game/Migrations/20180219060912_AddSkins.Designer.cs | 2 -- osu.Game/Migrations/20180219060912_AddSkins.cs | 2 -- .../20180529055154_RemoveUniqueHashConstraints.Designer.cs | 2 -- .../Migrations/20180529055154_RemoveUniqueHashConstraints.cs | 2 -- .../20180621044111_UpdateTaikoDefaultBindings.Designer.cs | 2 -- .../Migrations/20180621044111_UpdateTaikoDefaultBindings.cs | 2 -- .../Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs | 2 -- osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs | 2 -- osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs | 2 -- osu.Game/Migrations/20180913080842_AddRankStatus.cs | 2 -- osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs | 2 -- osu.Game/Migrations/20181007180454_StandardizePaths.cs | 2 -- osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs | 2 -- osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs | 2 -- .../Migrations/20181130113755_AddScoreInfoTables.Designer.cs | 2 -- osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs | 2 -- osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs | 2 -- osu.Game/Migrations/20190225062029_AddUserIDColumn.cs | 2 -- osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs | 2 -- osu.Game/Migrations/20190525060824_SkinSettings.cs | 2 -- .../20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs | 2 -- .../Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs | 2 -- .../20190708070844_AddBPMAndLengthColumns.Designer.cs | 2 -- osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs | 2 -- osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs | 2 -- osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs | 2 -- .../Migrations/20200302094919_RefreshVolumeBindings.Designer.cs | 2 -- osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs | 2 -- .../Migrations/20201019224408_AddEpilepsyWarning.Designer.cs | 2 -- osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs | 2 -- .../20210412045700_RefreshVolumeBindingsAgain.Designer.cs | 2 -- .../Migrations/20210412045700_RefreshVolumeBindingsAgain.cs | 2 -- .../20210511060743_AddSkinInstantiationInfo.Designer.cs | 2 -- osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs | 2 -- .../20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs | 2 -- .../Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs | 2 -- .../Migrations/20210824185035_AddCountdownSettings.Designer.cs | 2 -- osu.Game/Migrations/20210824185035_AddCountdownSettings.cs | 2 -- .../20210912144011_AddSamplesMatchPlaybackRate.Designer.cs | 2 -- .../Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs | 2 -- osu.Game/Migrations/20211020081609_ResetSkinHashes.cs | 2 -- osu.Game/Migrations/OsuDbContextModelSnapshot.cs | 2 -- 53 files changed, 106 deletions(-) diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs index 7104245d9e..c751530bf4 100644 --- a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs index f1c7c94638..08ab64fd08 100644 --- a/osu.Game/Migrations/20171019041408_InitialCreate.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs index b464c15ce2..4cd234f2ef 100644 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs index 1d0164a1d9..4ec3952941 100644 --- a/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs +++ b/osu.Game/Migrations/20171025071459_AddMissingIndexRules.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs index 53e8b887d8..006acf12cd 100644 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs index 765a6a5b58..6aba12f86f 100644 --- a/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs +++ b/osu.Game/Migrations/20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs index 4da20141bc..fc2496bc24 100644 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs index 3379efa68e..5688455f79 100644 --- a/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs +++ b/osu.Game/Migrations/20171209034410_AddRulesetInfoShortName.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180125143340_Settings.Designer.cs b/osu.Game/Migrations/20180125143340_Settings.Designer.cs index 68054d6404..4bb599eec1 100644 --- a/osu.Game/Migrations/20180125143340_Settings.Designer.cs +++ b/osu.Game/Migrations/20180125143340_Settings.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs index 8f7d0a6ed3..1feb37531f 100644 --- a/osu.Game/Migrations/20180125143340_Settings.cs +++ b/osu.Game/Migrations/20180125143340_Settings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs index 3e97e78e61..8646d1d76b 100644 --- a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs +++ b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Infrastructure; using osu.Game.Database; diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs index 7d95a702b8..cdc4ef2e66 100644 --- a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs +++ b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180219060912_AddSkins.cs b/osu.Game/Migrations/20180219060912_AddSkins.cs index df9d267a14..319748bed6 100644 --- a/osu.Game/Migrations/20180219060912_AddSkins.cs +++ b/osu.Game/Migrations/20180219060912_AddSkins.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs index 7e490c7833..f28408bfb3 100644 --- a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs @@ -1,7 +1,5 @@ // using Microsoft.EntityFrameworkCore; - -#nullable disable using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs index 62c341b0c6..91eabe8868 100644 --- a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs index a103cdad20..aaa11e88b6 100644 --- a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs index b08fa7a899..d888ccd5a2 100644 --- a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs +++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs index 5b1734554a..7eeacd56d7 100644 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs index 4ba0a4d0e6..fdea636ac6 100644 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs index ceef5b6948..5ab43da046 100644 --- a/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20180913080842_AddRankStatus.cs b/osu.Game/Migrations/20180913080842_AddRankStatus.cs index ec82925b03..bb147dff84 100644 --- a/osu.Game/Migrations/20180913080842_AddRankStatus.cs +++ b/osu.Game/Migrations/20180913080842_AddRankStatus.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs index d1185ef186..b387a45ecf 100644 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs index f9323c0b9b..30f27043a0 100644 --- a/osu.Game/Migrations/20181007180454_StandardizePaths.cs +++ b/osu.Game/Migrations/20181007180454_StandardizePaths.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs index 0797090c7b..120674671a 100644 --- a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs index 43689f2a3c..ee825a1e9c 100644 --- a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs index 3b70ad8d45..eee53182ce 100644 --- a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs index f52edacc7f..58980132f3 100644 --- a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs index 51b63bd9eb..8e1e3a59f3 100644 --- a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs index 00d807d5c4..f2eef600dc 100644 --- a/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs index 9d6515e5c6..348c42adb9 100644 --- a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs +++ b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs index 463d4fe1ad..7779b55bb7 100644 --- a/osu.Game/Migrations/20190525060824_SkinSettings.cs +++ b/osu.Game/Migrations/20190525060824_SkinSettings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs index 1bc2e76ae5..9477369aa0 100644 --- a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs index 61925a4cb4..0620a0624f 100644 --- a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs index c51b8739bc..c5fcc16f84 100644 --- a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs index 19a2464157..f8ce354aa1 100644 --- a/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs +++ b/osu.Game/Migrations/20190708070844_AddBPMAndLengthColumns.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs index 95583e7587..826233a2b0 100644 --- a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs index c1208e0bf1..af82b4db20 100644 --- a/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs +++ b/osu.Game/Migrations/20190913104727_AddBeatmapVideo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs index 3c7de0602b..22316b0380 100644 --- a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs index 73cfb1725b..3d2ddbf6fc 100644 --- a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs index 24acc3195f..1c05de832e 100644 --- a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs index eaca8785f9..58a35a7bf3 100644 --- a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs +++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs index 704af354c9..2c100d39b9 100644 --- a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs index 7772c0e86b..4d3941dd20 100644 --- a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs index 097bd6a244..b808c648da 100644 --- a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs index 1cc9ab6120..887635fa85 100644 --- a/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs +++ b/osu.Game/Migrations/20210511060743_AddSkinInstantiationInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs index ca9502422b..89bab3a0fa 100644 --- a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs index 82c5b27769..7b579e27b9 100644 --- a/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs +++ b/osu.Game/Migrations/20210514062639_AddAuthorIdToBeatmapMetadata.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs index 2a4e370649..afeb42130d 100644 --- a/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs +++ b/osu.Game/Migrations/20210824185035_AddCountdownSettings.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs index dd6fa23387..d1b09e2c1d 100644 --- a/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs +++ b/osu.Game/Migrations/20210824185035_AddCountdownSettings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs index 9e09f2c69e..6e53d7fae0 100644 --- a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs index 0598a41f09..f6fc1f4420 100644 --- a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs +++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations diff --git a/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs index 0f7a2a5702..6d53c019ec 100644 --- a/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs +++ b/osu.Game/Migrations/20211020081609_ResetSkinHashes.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using osu.Game.Database; diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 7e38889fa6..036c26cb0a 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -1,7 +1,5 @@ // using System; - -#nullable disable using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; From de7f2a0bba6953b486689427198b0b1fd2804a30 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Tue, 28 Jun 2022 20:56:02 +0800 Subject: [PATCH 204/363] Remove the nullable disable annotation in the localisation. --- osu.Game/Localisation/AudioSettingsStrings.cs | 2 -- osu.Game/Localisation/BeatmapOffsetControlStrings.cs | 2 -- osu.Game/Localisation/BindingSettingsStrings.cs | 2 -- osu.Game/Localisation/ButtonSystemStrings.cs | 2 -- osu.Game/Localisation/ChatStrings.cs | 2 -- osu.Game/Localisation/CommonStrings.cs | 2 -- osu.Game/Localisation/DebugLocalisationStore.cs | 2 -- osu.Game/Localisation/DebugSettingsStrings.cs | 2 -- osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs | 2 -- .../FirstRunOverlayImportFromStableScreenStrings.cs | 2 -- osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs | 2 -- osu.Game/Localisation/FirstRunSetupOverlayStrings.cs | 2 -- osu.Game/Localisation/GameplaySettingsStrings.cs | 2 -- osu.Game/Localisation/GeneralSettingsStrings.cs | 2 -- osu.Game/Localisation/GlobalActionKeyBindingStrings.cs | 2 -- osu.Game/Localisation/GraphicsSettingsStrings.cs | 2 -- osu.Game/Localisation/InputSettingsStrings.cs | 2 -- osu.Game/Localisation/JoystickSettingsStrings.cs | 2 -- osu.Game/Localisation/Language.cs | 2 -- osu.Game/Localisation/LayoutSettingsStrings.cs | 2 -- osu.Game/Localisation/LeaderboardStrings.cs | 2 -- osu.Game/Localisation/MaintenanceSettingsStrings.cs | 2 -- osu.Game/Localisation/ModSelectOverlayStrings.cs | 2 -- osu.Game/Localisation/MouseSettingsStrings.cs | 2 -- osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs | 2 -- osu.Game/Localisation/NamedOverlayComponentStrings.cs | 2 -- osu.Game/Localisation/NotificationsStrings.cs | 2 -- osu.Game/Localisation/NowPlayingStrings.cs | 2 -- osu.Game/Localisation/OnlineSettingsStrings.cs | 2 -- osu.Game/Localisation/RulesetSettingsStrings.cs | 2 -- osu.Game/Localisation/SettingsStrings.cs | 2 -- osu.Game/Localisation/SkinSettingsStrings.cs | 2 -- osu.Game/Localisation/TabletSettingsStrings.cs | 2 -- osu.Game/Localisation/ToastStrings.cs | 2 -- osu.Game/Localisation/UserInterfaceStrings.cs | 2 -- 35 files changed, 70 deletions(-) diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index 0dc95da2f4..0f0f560df9 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs index 417cf335e0..632a1ad0ea 100644 --- a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs +++ b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/BindingSettingsStrings.cs b/osu.Game/Localisation/BindingSettingsStrings.cs index 39b5ac0d21..ad4a650a1f 100644 --- a/osu.Game/Localisation/BindingSettingsStrings.cs +++ b/osu.Game/Localisation/BindingSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/ButtonSystemStrings.cs b/osu.Game/Localisation/ButtonSystemStrings.cs index c71a99711b..ba4abf63a6 100644 --- a/osu.Game/Localisation/ButtonSystemStrings.cs +++ b/osu.Game/Localisation/ButtonSystemStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index b07ed18f7b..7bd284a94e 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index 39dc7cf518..1ee562e122 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/DebugLocalisationStore.cs b/osu.Game/Localisation/DebugLocalisationStore.cs index 83ae4581a8..2b114b1bd8 100644 --- a/osu.Game/Localisation/DebugLocalisationStore.cs +++ b/osu.Game/Localisation/DebugLocalisationStore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Globalization; diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs index e6de4ddee9..74b2c8d892 100644 --- a/osu.Game/Localisation/DebugSettingsStrings.cs +++ b/osu.Game/Localisation/DebugSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs b/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs index 4bcbdcff7b..952ca22678 100644 --- a/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs +++ b/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs index 38f5860cc5..deac7d8628 100644 --- a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs +++ b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs b/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs index 331a8c6764..3a7fe4bb12 100644 --- a/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs +++ b/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs index 0c73d7a85b..91b427e2ca 100644 --- a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs +++ b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index 1719b7d6e8..8a0f773551 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs index 8cf26a930d..2aa91f5245 100644 --- a/osu.Game/Localisation/GeneralSettingsStrings.cs +++ b/osu.Game/Localisation/GeneralSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index d45fba6f17..82d03dbb5b 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index a73d67067e..38355d9041 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/InputSettingsStrings.cs b/osu.Game/Localisation/InputSettingsStrings.cs index a16dcd998a..e46b4cecf3 100644 --- a/osu.Game/Localisation/InputSettingsStrings.cs +++ b/osu.Game/Localisation/InputSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/JoystickSettingsStrings.cs b/osu.Game/Localisation/JoystickSettingsStrings.cs index efda1afd48..976ec1adde 100644 --- a/osu.Game/Localisation/JoystickSettingsStrings.cs +++ b/osu.Game/Localisation/JoystickSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index f9094b9540..c13a1a10cb 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.ComponentModel; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/LayoutSettingsStrings.cs b/osu.Game/Localisation/LayoutSettingsStrings.cs index 1a0b015050..b4326b8e39 100644 --- a/osu.Game/Localisation/LayoutSettingsStrings.cs +++ b/osu.Game/Localisation/LayoutSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/LeaderboardStrings.cs b/osu.Game/Localisation/LeaderboardStrings.cs index 14bc5b7af4..8e53f8e88c 100644 --- a/osu.Game/Localisation/LeaderboardStrings.cs +++ b/osu.Game/Localisation/LeaderboardStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs index 4cb514feb0..7a04bcd1ca 100644 --- a/osu.Game/Localisation/MaintenanceSettingsStrings.cs +++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index 3d99075922..e9af7147e3 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/MouseSettingsStrings.cs b/osu.Game/Localisation/MouseSettingsStrings.cs index 563d6f7637..fd7225ad2e 100644 --- a/osu.Game/Localisation/MouseSettingsStrings.cs +++ b/osu.Game/Localisation/MouseSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs b/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs index 956d5195e7..92cedce3e0 100644 --- a/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs +++ b/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/NamedOverlayComponentStrings.cs b/osu.Game/Localisation/NamedOverlayComponentStrings.cs index f6a50460dc..475bea2a4a 100644 --- a/osu.Game/Localisation/NamedOverlayComponentStrings.cs +++ b/osu.Game/Localisation/NamedOverlayComponentStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index c60e5d3415..382e0d81f4 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/NowPlayingStrings.cs b/osu.Game/Localisation/NowPlayingStrings.cs index 75d9b28ea6..f334637338 100644 --- a/osu.Game/Localisation/NowPlayingStrings.cs +++ b/osu.Game/Localisation/NowPlayingStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/OnlineSettingsStrings.cs b/osu.Game/Localisation/OnlineSettingsStrings.cs index ebb21afeb7..6862f4ac2c 100644 --- a/osu.Game/Localisation/OnlineSettingsStrings.cs +++ b/osu.Game/Localisation/OnlineSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/RulesetSettingsStrings.cs b/osu.Game/Localisation/RulesetSettingsStrings.cs index 08901a641e..a356c9e20b 100644 --- a/osu.Game/Localisation/RulesetSettingsStrings.cs +++ b/osu.Game/Localisation/RulesetSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/SettingsStrings.cs b/osu.Game/Localisation/SettingsStrings.cs index 5dcab39e10..aa2e2740eb 100644 --- a/osu.Game/Localisation/SettingsStrings.cs +++ b/osu.Game/Localisation/SettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/SkinSettingsStrings.cs b/osu.Game/Localisation/SkinSettingsStrings.cs index 42b72fdbb8..81035c5a5e 100644 --- a/osu.Game/Localisation/SkinSettingsStrings.cs +++ b/osu.Game/Localisation/SkinSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/TabletSettingsStrings.cs b/osu.Game/Localisation/TabletSettingsStrings.cs index 8d2bc21652..d62d348df9 100644 --- a/osu.Game/Localisation/TabletSettingsStrings.cs +++ b/osu.Game/Localisation/TabletSettingsStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 00a0031293..52e75425bf 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index a007f760d8..a090b8c14c 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Localisation; namespace osu.Game.Localisation From aacded0ecf2aaecaac5ea48e7fff00a9052064ca Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 28 Jun 2022 07:33:05 -0700 Subject: [PATCH 205/363] Fix difficulty adjust settings having more padding on sliders --- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index a42d4a049a..599e81f2b8 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods @@ -103,9 +104,9 @@ namespace osu.Game.Rulesets.Mods { InternalChildren = new Drawable[] { - new SettingsSlider + new OsuSliderBar { - ShowsDefaultIndicator = false, + RelativeSizeAxes = Axes.X, Current = currentNumber, KeyboardStep = 0.1f, } From f07107aba9e3812e8ae94a09fac8a00374da5e42 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 28 Jun 2022 08:38:15 -0700 Subject: [PATCH 206/363] Fix failing diff adjust slider tests --- .../UserInterface/TestSceneModDifficultyAdjustSettings.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index 72e503dc33..9641e6b1bf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -246,7 +247,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep($"Set {name} slider to {value}", () => this.ChildrenOfType().First(c => c.LabelText == name) - .ChildrenOfType>().First().Current.Value = value); + .ChildrenOfType>().First().Current.Value = value); } private void checkBindableAtValue(string name, float? expectedValue) @@ -260,7 +261,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddAssert($"Slider {name} at {expectedValue}", () => this.ChildrenOfType().First(c => c.LabelText == name) - .ChildrenOfType>().First().Current.Value == expectedValue); + .ChildrenOfType>().First().Current.Value == expectedValue); } private void setBeatmapWithDifficultyParameters(float value) From d07c2a64b934303314fec10956ec437a464cc6a2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 28 Jun 2022 08:39:09 -0700 Subject: [PATCH 207/363] Remove unused using --- .../Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index 9641e6b1bf..181f46a996 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -14,7 +14,6 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osuTK; From 978a80dd6f2b6bbc615ca520eac176da6e58ae88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 01:52:50 +0900 Subject: [PATCH 208/363] Change closure elimination to hint Pretty noisy as a suggestion, and can fire incorrectly quite a lot (aka `Schedule()`). --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index a55b3b5074..0794095854 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -98,6 +98,7 @@ WARNING HINT DO_NOT_SHOW + HINT HINT HINT ERROR From 36e71d39f2bbdd119148659e5682683f3bf07943 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 28 Jun 2022 09:54:18 -0700 Subject: [PATCH 209/363] Fix one more mod settings test --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 006707d064..4de70f6f9e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual.UserInterface createScreen(); AddStep("select diff adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); - AddStep("set setting", () => modSelectOverlay.ChildrenOfType>().First().Current.Value = 8); + AddStep("set setting", () => modSelectOverlay.ChildrenOfType>().First().Current.Value = 8); AddAssert("ensure setting is propagated", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); From ecdb30d215f57df0cbb608acafe676b25b5b25a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 02:28:21 +0900 Subject: [PATCH 210/363] Fix one more case of collection modification during enumeration https://sentry.ppy.sh/share/issue/a61c27b2a63a4a6aa80e75873f9d87ca/ --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 089a783177..cb34a92702 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -132,7 +132,7 @@ namespace osu.Game.Database Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}"); // Resubscribe any subscriptions - foreach (var action in customSubscriptionsResetMap.Keys) + foreach (var action in customSubscriptionsResetMap.Keys.ToArray()) registerSubscription(action); } From 3a00131606d11382b1ca264be4803cc7fb300ae3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 13:51:48 +0900 Subject: [PATCH 211/363] Bump fastlane --- Gemfile.lock | 60 ++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ddab497657..cae682ec2b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,20 +8,20 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.570.0) - aws-sdk-core (3.130.0) + aws-partitions (1.601.0) + aws-sdk-core (3.131.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.55.0) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.57.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.113.0) + aws-sdk-s3 (1.114.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sigv4 (1.4.0) + aws-sigv4 (1.5.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -36,7 +36,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) emoji_regex (3.2.3) - excon (0.92.1) + excon (0.92.3) faraday (1.10.0) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -56,8 +56,8 @@ GEM faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) + faraday-multipart (1.0.4) + multipart-post (~> 2) faraday-net_http (1.0.1) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) @@ -66,7 +66,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.205.1) + fastlane (2.206.2) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -110,9 +110,9 @@ GEM souyuz (= 0.11.1) fastlane-plugin-xamarin (0.6.3) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.16.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-core (0.4.2) + google-apis-androidpublisher_v3 (0.23.0) + google-apis-core (>= 0.6, < 2.a) + google-apis-core (0.6.0) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -121,19 +121,19 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.10.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-playcustomapp_v1 (0.7.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-storage_v1 (0.11.0) - google-apis-core (>= 0.4, < 2.a) + google-apis-iamcredentials_v1 (0.12.0) + google-apis-core (>= 0.6, < 2.a) + google-apis-playcustomapp_v1 (0.9.0) + google-apis-core (>= 0.6, < 2.a) + google-apis-storage_v1 (0.16.0) + google-apis-core (>= 0.6, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.2.0) - google-cloud-storage (1.36.1) + google-cloud-storage (1.36.2) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) @@ -141,7 +141,7 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.1.2) + googleauth (1.2.0) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -149,12 +149,12 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.4) + http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.6.1) - json (2.6.1) - jwt (2.3.0) + json (2.6.2) + jwt (2.4.1) memoist (0.16.2) mini_magick (4.11.0) mini_mime (1.1.2) @@ -169,10 +169,10 @@ GEM optparse (0.1.1) os (1.1.4) plist (3.6.0) - public_suffix (4.0.6) + public_suffix (4.0.7) racc (1.6.0) rake (13.0.6) - representable (3.1.1) + representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) @@ -182,9 +182,9 @@ GEM ruby2_keywords (0.0.5) rubyzip (2.3.2) security (0.1.3) - signet (0.16.1) + signet (0.17.0) addressable (~> 2.8) - faraday (>= 0.17.5, < 3.0) + faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) simctl (1.6.8) @@ -205,11 +205,11 @@ GEM uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8.1) + unf_ext (0.0.8.2) unicode-display_width (1.8.0) webrick (1.7.0) word_wrap (1.0.0) - xcodeproj (1.21.0) + xcodeproj (1.22.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) From 34b9118fb3bf83f7cb7055e322ef147267aeedb7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Jun 2022 16:08:59 +0900 Subject: [PATCH 212/363] Cleanup by using const value --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 9 +-------- .../Difficulty/Skills/OsuStrainSkill.cs | 8 +++++++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index f64c218c65..84ef109598 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -43,13 +43,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return currentStrain; } - public override double DifficultyValue() - { - double difficulty = GetCurrentStrainPeaks().Sum(); - - // 1.06 comes from the default DifficultyMultiplier field in OsuStrainSkill, - // and it's added here to keep values the same after Flashlight was converted from OsuStrainSkill. - return difficulty * 1.06; - } + public override double DifficultyValue() => GetCurrentStrainPeaks().Sum() * OsuStrainSkill.DEFAULT_DIFFICULTY_MULTIPLIER; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 94b5727e3f..d6683ade05 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -14,6 +14,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { public abstract class OsuStrainSkill : StrainSkill { + /// + /// The default multiplier applied by to the final difficulty value after all other calculations. + /// May be overridden via . + /// + public const double DEFAULT_DIFFICULTY_MULTIPLIER = 1.06; + /// /// The number of sections with the highest strains, which the peak strain reductions will apply to. /// This is done in order to decrease their impact on the overall difficulty of the map for this skill. @@ -28,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// /// The final multiplier to be applied to after all other calculations. /// - protected virtual double DifficultyMultiplier => 1.06; + protected virtual double DifficultyMultiplier => DEFAULT_DIFFICULTY_MULTIPLIER; protected OsuStrainSkill(Mod[] mods) : base(mods) From 0211fe7ae8d1e472158c2544d14b2ba6dff70daf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Jun 2022 16:29:14 +0900 Subject: [PATCH 213/363] Fix exception + possible div-by-0 --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6218d1a54a..c3b7834009 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); - double relevantAccuracy = (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0); + double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0); // Scale the speed value with accuracy and OD. speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 38cfc1ece5..3e0f2a5638 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -56,6 +56,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public double RelevantNoteCount() { + if (objectStrains.Count == 0) + return 0; + double maxStrain = objectStrains.Max(); return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Exp(-(next / maxStrain * 12.0 - 6.0))))); } From e6ccca804527f8032cf20dd0e8a84eb409b27735 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Jun 2022 16:29:17 +0900 Subject: [PATCH 214/363] Fix inspection --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index fab3fa8425..75d9469da3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; - double speedNotes = (skills[2] as Speed).RelevantNoteCount(); + double speedNotes = ((Speed)skills[2]).RelevantNoteCount(); double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; From ad95f037de2ead8f5da46f4f1201df5bb8cdde3a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Jun 2022 16:42:53 +0900 Subject: [PATCH 215/363] Prevent another case of potential div-by-0 --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 3e0f2a5638..76d93f536c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -60,6 +60,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return 0; double maxStrain = objectStrains.Max(); + + if (maxStrain == 0) + return 0; + return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Exp(-(next / maxStrain * 12.0 - 6.0))))); } } From 6d91c0f375369c182312bfde644cae26443339a9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Jun 2022 16:57:10 +0900 Subject: [PATCH 216/363] Resolve inspection issue --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 76d93f536c..a156726f94 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double DifficultyMultiplier => 1.04; private readonly double greatWindow; - private List objectStrains = new List(); + private readonly List objectStrains = new List(); public Speed(Mod[] mods, double hitWindowGreat) : base(mods) From 0e0e9968aca4027b8730a97942fdd71995dc4fd6 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Wed, 29 Jun 2022 01:23:35 -0700 Subject: [PATCH 217/363] Split ball and followcircle into default/legacy files --- .../TestSceneSliderApplication.cs | 5 +- .../Objects/Drawables/DrawableSlider.cs | 19 ++-- .../Drawables/DrawableSliderBall.cs} | 100 +++--------------- .../Skinning/Default/DefaultFollowCircle.cs | 33 ++++++ .../Skinning/Default/DefaultSliderBall.cs | 60 +++++++++++ .../Skinning/Legacy/LegacyFollowCircle.cs | 22 ++++ .../Legacy/OsuLegacySkinTransformer.cs | 10 +- 7 files changed, 147 insertions(+), 102 deletions(-) rename osu.Game.Rulesets.Osu/{Skinning/Default/SliderBall.cs => Objects/Drawables/DrawableSliderBall.cs} (67%) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs index 2981f1164d..08a62fe3ae 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osu.Game.Tests.Visual; using osuTK; @@ -93,10 +92,10 @@ namespace osu.Game.Rulesets.Osu.Tests }); AddStep("set accent white", () => dho.AccentColour.Value = Color4.White); - AddAssert("ball is white", () => dho.ChildrenOfType().Single().AccentColour == Color4.White); + AddAssert("ball is white", () => dho.ChildrenOfType().Single().AccentColour == Color4.White); AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red); - AddAssert("ball is red", () => dho.ChildrenOfType().Single().AccentColour == Color4.Red); + AddAssert("ball is red", () => dho.ChildrenOfType().Single().AccentColour == Color4.Red); } private Slider prepareObject(Slider slider) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 9e3b762690..91bb7f95f6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -29,7 +29,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderHead HeadCircle => headContainer.Child; public DrawableSliderTail TailCircle => tailContainer.Child; - public SliderBall Ball { get; private set; } + [Cached] + public DrawableSliderBall Ball { get; private set; } + public SkinnableDrawable Body { get; private set; } /// @@ -60,6 +62,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSlider([CanBeNull] Slider s = null) : base(s) { + Ball = new DrawableSliderBall + { + GetInitialHitAction = () => HeadCircle.HitAction, + BypassAutoSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0 + }; } [BackgroundDependencyLoader] @@ -73,13 +82,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, headContainer = new Container { RelativeSizeAxes = Axes.Both }, OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, }, - Ball = new SliderBall(this) - { - GetInitialHitAction = () => HeadCircle.HitAction, - BypassAutoSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0 - }, + Ball, slidingSample = new PausableSkinnableSound { Looping = true } }; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs similarity index 67% rename from osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index 389e9343e7..7bde60b39d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -7,24 +7,21 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; -namespace osu.Game.Rulesets.Osu.Skinning.Default +namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour + public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour { public Func GetInitialHitAction; @@ -34,14 +31,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default set => ball.Colour = value; } - private readonly Drawable followCircle; - private readonly Drawable fullSizeFollowCircle; - private readonly DrawableSlider drawableSlider; - private readonly Drawable ball; + private Drawable followCircle; + private Drawable followCircleReceptor; + private DrawableSlider drawableSlider; + private Drawable ball; - public SliderBall(DrawableSlider drawableSlider) + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableSlider) { - this.drawableSlider = drawableSlider; + this.drawableSlider = (DrawableSlider)drawableSlider; Origin = Anchor.Centre; @@ -49,15 +47,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Children = new[] { - followCircle = new FollowCircleContainer + followCircle = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()) { Origin = Anchor.Centre, Anchor = Anchor.Centre, RelativeSizeAxes = Axes.Both, Alpha = 0, - Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()), }, - fullSizeFollowCircle = new CircularContainer + followCircleReceptor = new CircularContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -106,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default tracking = value; - fullSizeFollowCircle.Scale = new Vector2(tracking ? 2.4f : 1f); + followCircleReceptor.Scale = new Vector2(tracking ? 2.4f : 1f); followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); @@ -167,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default // in valid time range Time.Current >= drawableSlider.HitObject.StartTime && Time.Current < drawableSlider.HitObject.EndTime && // in valid position range - lastScreenSpaceMousePosition.HasValue && fullSizeFollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && + lastScreenSpaceMousePosition.HasValue && followCircleReceptor.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && // valid action (actions?.Any(isValidTrackingAction) ?? false); @@ -205,74 +202,5 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI); lastPosition = Position; } - - private class FollowCircleContainer : CircularContainer - { - public override bool HandlePositionalInput => true; - } - - public class DefaultFollowCircle : CompositeDrawable - { - public DefaultFollowCircle() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = 5, - BorderColour = Color4.Orange, - Blending = BlendingParameters.Additive, - Child = new Box - { - Colour = Color4.Orange, - RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - } - }; - } - } - - public class DefaultSliderBall : CompositeDrawable - { - private Box box; - - [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject, ISkinSource skin) - { - var slider = (DrawableSlider)drawableObject; - - RelativeSizeAxes = Axes.Both; - - float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; - - InternalChild = new CircularContainer - { - Masking = true, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Blending = BlendingParameters.Additive, - BorderThickness = 10, - BorderColour = Color4.White, - Alpha = 1, - Child = box = new Box - { - Blending = BlendingParameters.Additive, - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - AlwaysPresent = true, - Alpha = 0 - } - }; - - slider.Tracking.BindValueChanged(trackingChanged, true); - } - - private void trackingChanged(ValueChangedEvent tracking) => - box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint); - } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs new file mode 100644 index 0000000000..8211448705 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -0,0 +1,33 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public class DefaultFollowCircle : CompositeDrawable + { + public DefaultFollowCircle() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 5, + BorderColour = Color4.Orange, + Blending = BlendingParameters.Additive, + Child = new Box + { + Colour = Color4.Orange, + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs new file mode 100644 index 0000000000..47308375e6 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public class DefaultSliderBall : CompositeDrawable + { + private Box box; + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject, ISkinSource skin) + { + var slider = (DrawableSlider)drawableObject; + + RelativeSizeAxes = Axes.Both; + + float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; + + InternalChild = new CircularContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + BorderThickness = 10, + BorderColour = Color4.White, + Alpha = 1, + Child = box = new Box + { + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + AlwaysPresent = true, + Alpha = 0 + } + }; + + slider.Tracking.BindValueChanged(trackingChanged, true); + } + + private void trackingChanged(ValueChangedEvent tracking) => + box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint); + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs new file mode 100644 index 0000000000..b8a559ce07 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -0,0 +1,22 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + public class LegacyFollowCircle : CompositeDrawable + { + public LegacyFollowCircle(Drawable animationContent) + { + // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x + animationContent.Scale *= 0.5f; + animationContent.Anchor = Anchor.Centre; + animationContent.Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + InternalChild = animationContent; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index e7d28a9bd7..885a2c12fb 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -41,11 +41,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return this.GetAnimation(component.LookupName, false, false); case OsuSkinComponents.SliderFollowCircle: - var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true); - if (followCircle != null) - // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x - followCircle.Scale *= 0.5f; - return followCircle; + var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true); + if (followCircleContent != null) + return new LegacyFollowCircle(followCircleContent); + + return null; case OsuSkinComponents.SliderBall: var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: ""); From b092e6937a6d9fbec870b2804470935b0392466b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 18:51:07 +0900 Subject: [PATCH 218/363] Guard against ruleset icon creation failures to avoid whole game death --- osu.Game/Rulesets/RulesetSelector.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetSelector.cs b/osu.Game/Rulesets/RulesetSelector.cs index 35244eb86e..1909e741f0 100644 --- a/osu.Game/Rulesets/RulesetSelector.cs +++ b/osu.Game/Rulesets/RulesetSelector.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Allocation; +using osu.Framework.Logging; namespace osu.Game.Rulesets { @@ -19,7 +20,16 @@ namespace osu.Game.Rulesets private void load() { foreach (var r in Rulesets.AvailableRulesets) - AddItem(r); + { + try + { + AddItem(r); + } + catch + { + Logger.Log($"Could not create ruleset icon for {r.Name}. Please check for an update.", level: LogLevel.Error); + } + } } } } From 6c64cea057121dbb49458eb21e69c7ae9ddfbb72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 18:56:15 +0900 Subject: [PATCH 219/363] Catch a second location --- osu.Game/Overlays/Settings/SettingsFooter.cs | 24 +++++++++++++------- osu.Game/Rulesets/RulesetSelector.cs | 6 ++--- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index 2f182d537f..b9ac1aa53b 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -32,16 +33,23 @@ namespace osu.Game.Overlays.Settings foreach (var ruleset in rulesets.AvailableRulesets) { - var icon = new ConstrainedIconContainer + try { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Icon = ruleset.CreateInstance().CreateIcon(), - Colour = Color4.Gray, - Size = new Vector2(20), - }; + var icon = new ConstrainedIconContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Icon = ruleset.CreateInstance().CreateIcon(), + Colour = Color4.Gray, + Size = new Vector2(20), + }; - modes.Add(icon); + modes.Add(icon); + } + catch + { + Logger.Log($"Could not create ruleset icon for {ruleset.Name}. Please check for an update from the developer.", level: LogLevel.Error); + } } Children = new Drawable[] diff --git a/osu.Game/Rulesets/RulesetSelector.cs b/osu.Game/Rulesets/RulesetSelector.cs index 1909e741f0..701e60eec9 100644 --- a/osu.Game/Rulesets/RulesetSelector.cs +++ b/osu.Game/Rulesets/RulesetSelector.cs @@ -19,15 +19,15 @@ namespace osu.Game.Rulesets [BackgroundDependencyLoader] private void load() { - foreach (var r in Rulesets.AvailableRulesets) + foreach (var ruleset in Rulesets.AvailableRulesets) { try { - AddItem(r); + AddItem(ruleset); } catch { - Logger.Log($"Could not create ruleset icon for {r.Name}. Please check for an update.", level: LogLevel.Error); + Logger.Log($"Could not create ruleset icon for {ruleset.Name}. Please check for an update from the developer.", level: LogLevel.Error); } } } From e3eba58cb644773817e57b2f94ae9e9781bbd913 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 29 Jun 2022 13:37:17 +0300 Subject: [PATCH 220/363] Catch in triangles intro --- osu.Game/Screens/Menu/IntroTriangles.cs | 34 ++++++++++++++----------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index d59a07a350..ad098ae8df 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Collections.Generic; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -14,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; +using osu.Framework.Logging; using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Graphics; @@ -340,24 +340,28 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - var modes = new List(); - - foreach (var ruleset in rulesets.AvailableRulesets) - { - var icon = new ConstrainedIconContainer - { - Icon = ruleset.CreateInstance().CreateIcon(), - Size = new Vector2(30), - }; - - modes.Add(icon); - } - AutoSizeAxes = Axes.Both; - Children = modes; Anchor = Anchor.Centre; Origin = Anchor.Centre; + + foreach (var ruleset in rulesets.AvailableRulesets) + { + try + { + var icon = new ConstrainedIconContainer + { + Icon = ruleset.CreateInstance().CreateIcon(), + Size = new Vector2(30), + }; + + Add(icon); + } + catch + { + Logger.Log($"Could not create ruleset icon for {ruleset.Name}. Please check for an update from the developer.", level: LogLevel.Error); + } + } } } From 87e3b4418113831cb612109a4813bfac115b8e86 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 29 Jun 2022 13:37:02 +0300 Subject: [PATCH 221/363] Ensure icon is loaded inside try-catch --- osu.Game/Overlays/Settings/SettingsFooter.cs | 56 ++++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index b9ac1aa53b..db0dc8fd5e 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -3,7 +3,6 @@ #nullable disable -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Development; using osu.Framework.Graphics; @@ -29,7 +28,33 @@ namespace osu.Game.Overlays.Settings Direction = FillDirection.Vertical; Padding = new MarginPadding { Top = 20, Bottom = 30, Horizontal = SettingsPanel.CONTENT_MARGINS }; - var modes = new List(); + FillFlowContainer modes; + + Children = new Drawable[] + { + modes = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(5), + Padding = new MarginPadding { Bottom = 10 }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = game.Name, + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), + }, + new BuildDisplay(game.Version, DebugUtils.IsDebugBuild) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + }; foreach (var ruleset in rulesets.AvailableRulesets) { @@ -51,33 +76,6 @@ namespace osu.Game.Overlays.Settings Logger.Log($"Could not create ruleset icon for {ruleset.Name}. Please check for an update from the developer.", level: LogLevel.Error); } } - - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = modes, - Spacing = new Vector2(5), - Padding = new MarginPadding { Bottom = 10 }, - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = game.Name, - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), - }, - new BuildDisplay(game.Version, DebugUtils.IsDebugBuild) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - } - }; } private class BuildDisplay : OsuAnimatedButton From 32af4e41eaa3805ddc2ffb94909f289877aa1996 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 20:56:01 +0900 Subject: [PATCH 222/363] Add back thread safety and locking as required --- osu.Game/Database/RealmAccess.cs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 715066f32d..728cd5c7c3 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -446,8 +446,12 @@ namespace osu.Game.Database { Func action = realm => query(realm).QueryAsyncWithNotifications(callback); - // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. - notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); + lock (notificationsResetMap) + { + // Store an action which is used when blocking to ensure consumers don't use results of a stale changeset firing. + notificationsResetMap.Add(action, () => callback(new EmptyRealmSet(), null, null)); + } + return RegisterCustomSubscription(action); } @@ -543,7 +547,12 @@ namespace osu.Game.Database { unsubscriptionAction?.Dispose(); customSubscriptionsResetMap.Remove(action); - notificationsResetMap.Remove(action); + + lock (notificationsResetMap) + { + notificationsResetMap.Remove(action); + } + total_subscriptions.Value--; } } @@ -805,6 +814,9 @@ namespace osu.Game.Database /// An which should be disposed to end the blocking section. public IDisposable BlockAllOperations() { + if (!ThreadSafety.IsUpdateThread) + throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); + if (isDisposed) throw new ObjectDisposedException(nameof(RealmAccess)); @@ -816,9 +828,6 @@ namespace osu.Game.Database if (hasInitialisedOnce) { - if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); - syncContext = SynchronizationContext.Current; // Before disposing the update context, clean up all subscriptions. From 18d465eff7dc30e4d9c5ad5ee6a0d277e2012dc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 21:02:23 +0900 Subject: [PATCH 223/363] Guard against `NaN` star difficulty results --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 2 +- osu.Game/Beatmaps/StarDifficulty.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index ef0fa36b16..611b47c120 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -207,7 +207,7 @@ namespace osu.Game.Beatmaps if (cancellationToken.IsCancellationRequested) return; - var starDifficulty = task.GetResultSafely(); + StarDifficulty? starDifficulty = task.GetResultSafely(); if (starDifficulty != null) bindable.Value = starDifficulty.Value; diff --git a/osu.Game/Beatmaps/StarDifficulty.cs b/osu.Game/Beatmaps/StarDifficulty.cs index e042f1c698..6aac275a6a 100644 --- a/osu.Game/Beatmaps/StarDifficulty.cs +++ b/osu.Game/Beatmaps/StarDifficulty.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps /// public StarDifficulty([NotNull] DifficultyAttributes attributes) { - Stars = attributes.StarRating; + Stars = double.IsFinite(attributes.StarRating) ? attributes.StarRating : 0; MaxCombo = attributes.MaxCombo; Attributes = attributes; // Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...) @@ -46,7 +46,7 @@ namespace osu.Game.Beatmaps /// public StarDifficulty(double starDifficulty, int maxCombo) { - Stars = starDifficulty; + Stars = double.IsFinite(starDifficulty) ? starDifficulty : 0; MaxCombo = maxCombo; Attributes = null; } From 7cb4e32c17ffb61c91c7b5d9e84c1f0729839e33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 22:45:19 +0900 Subject: [PATCH 224/363] Add one more lock to appease CI --- osu.Game/Database/RealmAccess.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 59a235dbc2..8cf9bb4a47 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -880,8 +880,11 @@ namespace osu.Game.Database try { - foreach (var action in notificationsResetMap.Values) - action(); + lock (notificationsResetMap) + { + foreach (var action in notificationsResetMap.Values) + action(); + } } finally { From 582175c3a429e36e4d58798693518c0fffc3508d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jun 2022 22:57:52 +0900 Subject: [PATCH 225/363] 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 2ef3bb2dd1..584fe0a3ef 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 ab690b22a6..ff223f5107 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 a84fc40a45..b8a4aca02e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 13108155d3e4ef0ca2e19d520e9ddfcf2c8a4b95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 03:43:56 +0900 Subject: [PATCH 226/363] Fix crash on visual esting `TestSceneChatOverlay` due to null user --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index d2d9b9a9e5..247ea52648 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -39,7 +39,8 @@ namespace osu.Game.Tests.Visual.Online private TestChatOverlay chatOverlay; private ChannelManager channelManager; - private APIUser testUser; + private readonly APIUser testUser = new APIUser { Username = "test user", Id = 5071479 }; + private Channel[] testChannels; private Channel testChannel1 => testChannels[0]; @@ -51,7 +52,6 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void SetUp() => Schedule(() => { - testUser = new APIUser { Username = "test user", Id = 5071479 }; testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray(); Child = new DependencyProvidingContainer From 46cbbf5c6eb240b416ee24445ed8aa5beeead54a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 04:45:02 +0900 Subject: [PATCH 227/363] Fix flaky song select placeholder test by changing ruleset post-display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was failing occasionally due to the beatmap present operation causing the test's ruleset change to undo. ```csharp TearDown : System.TimeoutException : "wait for placeholder visible" timed out --TearDown at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) --- End of stack trace from previous location --- at osu.Framework.Testing.TestSceneTestRunner.TestRunner.RunTestBlocking(TestScene test) at osu.Game.Tests.Visual.OsuTestScene.OsuTestSceneTestRunner.RunTestBlocking(TestScene test) in /opt/buildagent/work/ecd860037212ac52/osu.Game/Tests/Visual/OsuTestScene.cs:line 503 at osu.Framework.Testing.TestScene.RunTestsFromNUnit() ------- Stdout: ------- [runtime] 2022-06-29 10:36:45 [verbose]: 💨 Class: TestScenePlaySongSelect [runtime] 2022-06-29 10:36:45 [verbose]: 🔶 Test: TestPlaceholderConvertSetting [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #1 exit all screens [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #2 reset defaults [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #3 delete all beatmaps [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #4 change ruleset to 2 [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #5 import test map for ruleset 0 [database] 2022-06-29 10:36:45 [verbose]: [efc1a] Beginning import from unknown... [database] 2022-06-29 10:36:45 [verbose]: [efc1a] Import successfully completed! [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #6 wait for imported to arrive in carousel [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #7 change convert setting [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #8 create song select [runtime] 2022-06-29 10:36:45 [verbose]: ScreenTestScene screen changed → TestScenePlaySongSelect+TestSongSelect [runtime] 2022-06-29 10:36:45 [verbose]: 📺 ScreenTestScene(OsuScreenStack)#338(depth:1) loading TestScenePlaySongSelect+TestSongSelect#263 [runtime] 2022-06-29 10:36:45 [verbose]: decoupled ruleset transferred ("" -> "osu!catch") [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #9 wait for present [runtime] 2022-06-29 10:36:45 [verbose]: 📺 ScreenTestScene(OsuScreenStack)#338(depth:1) entered TestScenePlaySongSelect+TestSongSelect#263 [runtime] 2022-06-29 10:36:45 [verbose]: 📺 BackgroundScreenStack#328(depth:1) loading BackgroundScreenBeatmap#338 [runtime] 2022-06-29 10:36:45 [verbose]: 📺 BackgroundScreenStack#328(depth:1) entered BackgroundScreenBeatmap#338 [runtime] 2022-06-29 10:36:45 [verbose]: Song select updating selection with beatmap:null ruleset:fruits [runtime] 2022-06-29 10:36:45 [verbose]: Song select changing beatmap from "please load a beatmap! - no beatmaps available!" to "null" [runtime] 2022-06-29 10:36:45 [verbose]: Song select working beatmap updated to Some Artist 0 - Some Song (set id 6224) ece7b702-895c-4f15-892f-05e4ff5e9a24 (Some Guy 3) [Normal 6224000 (length 0:50, bpm 106.7)] [runtime] 2022-06-29 10:36:45 [verbose]: Song select updating selection with beatmap:null ruleset:osu [runtime] 2022-06-29 10:36:45 [verbose]: decoupled ruleset transferred ("osu!catch" -> "osu!") [runtime] 2022-06-29 10:36:45 [verbose]: Song select updating selection with beatmap:74232aa2-2a1f-4920-b643-e85976838251 ruleset:osu [runtime] 2022-06-29 10:36:45 [verbose]: Song select decided to ensurePlayingSelected [runtime] 2022-06-29 10:36:45 [verbose]: Game-wide working beatmap updated to Some Artist 0 - Some Song (set id 6224) ece7b702-895c-4f15-892f-05e4ff5e9a24 (Some Guy 3) [Normal 6224000 (length 0:50, bpm 106.7)] [runtime] 2022-06-29 10:36:45 [debug]: Focus changed from nothing to SeekLimitedSearchTextBox. [network] 2022-06-29 10:36:45 [verbose]: Failing request osu.Game.Online.API.Requests.GetBeatmapRequest (System.InvalidOperationException: DummyAPIAccess cannot process this request.) [runtime] 2022-06-29 10:36:45 [verbose]: ✔️ 16 repetitions [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #10 wait for carousel loaded [runtime] 2022-06-29 10:36:45 [verbose]: 🔸 Step #11 wait for placeholder visible [runtime] 2022-06-29 10:36:55 [verbose]: 💥 Failed (on attempt 1,459) [runtime] 2022-06-29 10:36:55 [verbose]: ⏳ Currently loading components (0) [runtime] 2022-06-29 10:36:55 [verbose]: 🧵 Task schedulers [runtime] 2022-06-29 10:36:55 [verbose]: LoadComponentsAsync (standard) concurrency:4 running:0 pending:0 [runtime] 2022-06-29 10:36:55 [verbose]: LoadComponentsAsync (long load) concurrency:4 running:0 pending:0 [runtime] 2022-06-29 10:36:55 [verbose]: 🎱 Thread pool [runtime] 2022-06-29 10:36:55 [verbose]: worker: min 1 max 32,767 available 32,766 [runtime] 2022-06-29 10:36:55 [verbose]: completion: min 1 max 1,000 available 1,000 [runtime] 2022-06-29 10:36:55 [debug]: Focus on "SeekLimitedSearchTextBox" no longer valid as a result of unfocusIfNoLongerValid. [runtime] 2022-06-29 10:36:55 [debug]: Focus changed from SeekLimitedSearchTextBox to nothing. ``` --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6d881555da..6896b442e0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -100,12 +100,13 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestPlaceholderConvertSetting() { - changeRuleset(2); addRulesetImportStep(0); AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); createSongSelect(); + changeRuleset(2); + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); AddStep("click link in placeholder", () => getPlaceholder().ChildrenOfType().First().TriggerClick()); From 8171deec494d8b1a2f9794453bb54f8d88821ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 Jun 2022 22:38:06 +0200 Subject: [PATCH 228/363] Attempt to fix editor navigation test failures again First of all, stop relying that `InputManager` will successfully press the gameplay test button, because it won't if it's obscured by the notification overlay. Closing the overlay in a loop doesn't work because there's a chance that a notification is posted, then all overlays are closed, then another notification is posted, and so the button still isn't clickable. Instead, use the `TestGameplay()` method directly. Secondly, the notifications will still block `EditorPlayerLoader` from transitioning to `EditorPlayer`. Here we have no other choice than to aggressively dismiss notifications every spin of the relevant until step and hope we eventually progress to `EditorPlayer`. --- .../Editing/TestSceneEditorNavigation.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs index a21ef6f897..327d581e37 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs @@ -6,18 +6,14 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.GameplayTest; using osu.Game.Screens.Select; using osu.Game.Tests.Resources; -using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { @@ -36,20 +32,21 @@ namespace osu.Game.Tests.Visual.Editing () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.IsLoaded); - AddUntilStep("wait for completion notification", () => Game.Notifications.ChildrenOfType().Count() == 1); - AddStep("dismiss notifications", () => Game.Notifications.Hide()); AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); - AddStep("test gameplay", () => + AddStep("test gameplay", () => ((Editor)Game.ScreenStack.CurrentScreen).TestGameplay()); + + AddUntilStep("wait for player", () => { - var testGameplayButton = this.ChildrenOfType().Single(); - InputManager.MoveMouseTo(testGameplayButton); - InputManager.Click(MouseButton.Left); + // notifications may fire at almost any inopportune time and cause annoying test failures. + // relentlessly attempt to dismiss any and all interfering overlays, which includes notifications. + // this is theoretically not foolproof, but it's the best that can be done here. + Game.CloseAllOverlays(); + return Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded; }); - AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded); AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield())); From c5d31f50cd4053adedaa4787edc0fcb1222bbc6e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 30 Jun 2022 11:21:15 +0900 Subject: [PATCH 229/363] Attempt to fix flaky TestSceneAutoplay test --- .../Visual/Gameplay/TestSceneAutoplay.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 019bfe322e..47c8dc0f8d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -6,10 +6,10 @@ using System.ComponentModel; using System.Linq; using osu.Framework.Testing; -using osu.Game.Beatmaps.Timing; using osu.Game.Graphics.Containers; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.Break; @@ -31,19 +31,20 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { + // It doesn't matter which ruleset is used - this beatmap is only used for reference. + var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); - seekToBreak(0); + + seekTo(beatmap.Beatmap.Breaks[0].StartTime); AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); + AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); - seekToBreak(0); - seekToBreak(1); - - AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); - + seekTo(beatmap.Beatmap.HitObjects[^1].GetEndTime()); AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true); AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100); @@ -58,12 +59,18 @@ namespace osu.Game.Tests.Visual.Gameplay ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen; } - private void seekToBreak(int breakIndex) + private void seekTo(double time) { - AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime)); - AddUntilStep("wait for seek to complete", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= destBreak().StartTime); + AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time)); - BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex); + // Prevent test timeouts by seeking in 10 second increments. + for (double t = 0; t < time; t += 10000) + { + double expectedTime = t; + AddUntilStep($"current time >= {t}", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= expectedTime); + } + + AddUntilStep("wait for seek to complete", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= time); } } } From edc4ace17e4f703b6ee0b362e943388d25262748 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 30 Jun 2022 11:52:29 +0900 Subject: [PATCH 230/363] Attach DT with NC, SD with PF, and Cinema with Autoplay --- osu.Game/Rulesets/Ruleset.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index e098472999..c1ec6c30ef 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets break; case ModPerfect: - value |= LegacyMods.Perfect; + value |= LegacyMods.Perfect | LegacyMods.SuddenDeath; break; case ModSuddenDeath: @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets break; case ModNightcore: - value |= LegacyMods.Nightcore; + value |= LegacyMods.Nightcore | LegacyMods.DoubleTime; break; case ModDoubleTime: @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets break; case ModCinema: - value |= LegacyMods.Cinema; + value |= LegacyMods.Cinema | LegacyMods.Autoplay; break; case ModAutoplay: From 6c2d02fefdd5498553ca65b7546f717546e06c0d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 30 Jun 2022 12:37:03 +0900 Subject: [PATCH 231/363] Fix tests --- .../CatchLegacyModConversionTest.cs | 15 +++++++++------ .../ManiaLegacyModConversionTest.cs | 9 ++++++--- .../OsuLegacyModConversionTest.cs | 9 ++++++--- .../TaikoLegacyModConversionTest.cs | 15 +++++++++------ 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs index c65c9df9f9..b9d6f28228 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs @@ -24,21 +24,24 @@ namespace osu.Game.Rulesets.Catch.Tests new object[] { LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) } }, new object[] { LegacyMods.Relax, new[] { typeof(CatchModRelax) } }, new object[] { LegacyMods.HalfTime, new[] { typeof(CatchModHalfTime) } }, - new object[] { LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) } }, new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } }, new object[] { LegacyMods.Autoplay, new[] { typeof(CatchModAutoplay) } }, - new object[] { LegacyMods.Perfect, new[] { typeof(CatchModPerfect) } }, - new object[] { LegacyMods.Cinema, new[] { typeof(CatchModCinema) } }, new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } } }; + [TestCaseSource(nameof(catch_mod_mapping))] + [TestCase(LegacyMods.Cinema, new[] { typeof(CatchModCinema) })] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(CatchModCinema) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(CatchModPerfect) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })] + public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); + [TestCaseSource(nameof(catch_mod_mapping))] [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(CatchModCinema) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })] - public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); - - [TestCaseSource(nameof(catch_mod_mapping))] public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); protected override Ruleset CreateRuleset() => new CatchRuleset(); diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs index accae29ffe..9dee861e66 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs @@ -23,10 +23,8 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { LegacyMods.SuddenDeath, new[] { typeof(ManiaModSuddenDeath) } }, new object[] { LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime) } }, new object[] { LegacyMods.HalfTime, new[] { typeof(ManiaModHalfTime) } }, - new object[] { LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) } }, new object[] { LegacyMods.Flashlight, new[] { typeof(ManiaModFlashlight) } }, new object[] { LegacyMods.Autoplay, new[] { typeof(ManiaModAutoplay) } }, - new object[] { LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) } }, new object[] { LegacyMods.Key4, new[] { typeof(ManiaModKey4) } }, new object[] { LegacyMods.Key5, new[] { typeof(ManiaModKey5) } }, new object[] { LegacyMods.Key6, new[] { typeof(ManiaModKey6) } }, @@ -34,7 +32,6 @@ namespace osu.Game.Rulesets.Mania.Tests new object[] { LegacyMods.Key8, new[] { typeof(ManiaModKey8) } }, new object[] { LegacyMods.FadeIn, new[] { typeof(ManiaModFadeIn) } }, new object[] { LegacyMods.Random, new[] { typeof(ManiaModRandom) } }, - new object[] { LegacyMods.Cinema, new[] { typeof(ManiaModCinema) } }, new object[] { LegacyMods.Key9, new[] { typeof(ManiaModKey9) } }, new object[] { LegacyMods.KeyCoop, new[] { typeof(ManiaModDualStages) } }, new object[] { LegacyMods.Key1, new[] { typeof(ManiaModKey1) } }, @@ -45,12 +42,18 @@ namespace osu.Game.Rulesets.Mania.Tests }; [TestCaseSource(nameof(mania_mod_mapping))] + [TestCase(LegacyMods.Cinema, new[] { typeof(ManiaModCinema) })] [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(ManiaModCinema) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })] public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); [TestCaseSource(nameof(mania_mod_mapping))] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(ManiaModCinema) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })] public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); protected override Ruleset CreateRuleset() => new ManiaRuleset(); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs index db9872b152..01d83b55e6 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs @@ -25,24 +25,27 @@ namespace osu.Game.Rulesets.Osu.Tests new object[] { LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime) } }, new object[] { LegacyMods.Relax, new[] { typeof(OsuModRelax) } }, new object[] { LegacyMods.HalfTime, new[] { typeof(OsuModHalfTime) } }, - new object[] { LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) } }, new object[] { LegacyMods.Flashlight, new[] { typeof(OsuModFlashlight) } }, new object[] { LegacyMods.Autoplay, new[] { typeof(OsuModAutoplay) } }, new object[] { LegacyMods.SpunOut, new[] { typeof(OsuModSpunOut) } }, new object[] { LegacyMods.Autopilot, new[] { typeof(OsuModAutopilot) } }, - new object[] { LegacyMods.Perfect, new[] { typeof(OsuModPerfect) } }, - new object[] { LegacyMods.Cinema, new[] { typeof(OsuModCinema) } }, new object[] { LegacyMods.Target, new[] { typeof(OsuModTarget) } }, new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) } } }; [TestCaseSource(nameof(osu_mod_mapping))] + [TestCase(LegacyMods.Cinema, new[] { typeof(OsuModCinema) })] [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(OsuModCinema) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(OsuModPerfect) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })] public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); [TestCaseSource(nameof(osu_mod_mapping))] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(OsuModCinema) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })] public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); protected override Ruleset CreateRuleset() => new OsuRuleset(); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs index 3553cb27dc..c86f8cb8d2 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoLegacyModConversionTest.cs @@ -24,22 +24,25 @@ namespace osu.Game.Rulesets.Taiko.Tests new object[] { LegacyMods.DoubleTime, new[] { typeof(TaikoModDoubleTime) } }, new object[] { LegacyMods.Relax, new[] { typeof(TaikoModRelax) } }, new object[] { LegacyMods.HalfTime, new[] { typeof(TaikoModHalfTime) } }, - new object[] { LegacyMods.Nightcore, new[] { typeof(TaikoModNightcore) } }, new object[] { LegacyMods.Flashlight, new[] { typeof(TaikoModFlashlight) } }, new object[] { LegacyMods.Autoplay, new[] { typeof(TaikoModAutoplay) } }, - new object[] { LegacyMods.Perfect, new[] { typeof(TaikoModPerfect) } }, new object[] { LegacyMods.Random, new[] { typeof(TaikoModRandom) } }, - new object[] { LegacyMods.Cinema, new[] { typeof(TaikoModCinema) } }, new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(TaikoModHardRock), typeof(TaikoModDoubleTime) } } }; + [TestCaseSource(nameof(taiko_mod_mapping))] + [TestCase(LegacyMods.Cinema, new[] { typeof(TaikoModCinema) })] + [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(TaikoModCinema) })] + [TestCase(LegacyMods.Nightcore, new[] { typeof(TaikoModNightcore) })] + [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModNightcore) })] + [TestCase(LegacyMods.Perfect, new[] { typeof(TaikoModPerfect) })] + [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(TaikoModPerfect) })] + public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); + [TestCaseSource(nameof(taiko_mod_mapping))] [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(TaikoModCinema) })] [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(TaikoModNightcore) })] [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(TaikoModPerfect) })] - public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods); - - [TestCaseSource(nameof(taiko_mod_mapping))] public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods); protected override Ruleset CreateRuleset() => new TaikoRuleset(); From c6520de749daeaaf6b7e1e8962d7c46d25491aa4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 30 Jun 2022 14:24:49 +0900 Subject: [PATCH 232/363] Ensure PlaylistItem beatmap is not null --- osu.Game/Online/Rooms/PlaylistItem.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index a0711d9ded..2213311c67 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -72,7 +72,7 @@ namespace osu.Game.Online.Rooms /// In many cases, this will *not* contain any usable information apart from OnlineID. /// [JsonIgnore] - public IBeatmapInfo Beatmap { get; set; } = null!; + public IBeatmapInfo Beatmap { get; private set; } [JsonIgnore] public IBindable Valid => valid; @@ -81,6 +81,7 @@ namespace osu.Game.Online.Rooms [JsonConstructor] private PlaylistItem() + : this(new APIBeatmap()) { } From 0abb400f6400e8ceca40bbf34950aa1671a06eb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 15:14:25 +0900 Subject: [PATCH 233/363] Add reproduction of transaction not being exited correctly --- osu.Game.Tests/Database/RealmLiveTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 00a667521d..fd1f564f59 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -91,6 +91,25 @@ namespace osu.Game.Tests.Database Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); } + [Test] + public void TestTransactionRolledBackOnException() + { + RunTestWithRealm((realm, _) => + { + var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); + + realm.Run(r => r.Write(_ => r.Add(beatmap))); + + var liveBeatmap = beatmap.ToLive(realm); + + Assert.Throws(() => liveBeatmap.PerformWrite(l => throw new InvalidOperationException())); + Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); + + liveBeatmap.PerformWrite(l => l.Hidden = true); + Assert.IsTrue(liveBeatmap.PerformRead(l => l.Hidden)); + }); + } + [Test] public void TestScopedReadWithoutContext() { From 78d86fd3ffef5f61b06ea557ab777a0990174762 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 15:11:22 +0900 Subject: [PATCH 234/363] Fix `PerformWrite` not rolling back transaction on exception --- osu.Game/Database/RealmLive.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 72de747807..9c871a3929 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -104,9 +104,12 @@ namespace osu.Game.Database PerformRead(t => { - var transaction = t.Realm.BeginWrite(); - perform(t); - transaction.Commit(); + using (var transaction = t.Realm.BeginWrite()) + { + perform(t); + transaction.Commit(); + } + RealmLiveStatistics.WRITES.Value++; }); } From 356c0501ec25596ed62dc0c8ae0c4af3cf56878f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 15:40:44 +0900 Subject: [PATCH 235/363] Guard again potential nulls in `RealmNamedFileUsage` Hopefully help in figuring out https://sentry.ppy.sh/organizations/ppy/issues/3679/?project=2&query=user%3A%22id%3A10078484%22 --- osu.Game/Models/RealmNamedFileUsage.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Models/RealmNamedFileUsage.cs b/osu.Game/Models/RealmNamedFileUsage.cs index 0f6f439d73..c4310c4edb 100644 --- a/osu.Game/Models/RealmNamedFileUsage.cs +++ b/osu.Game/Models/RealmNamedFileUsage.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; using JetBrains.Annotations; using osu.Framework.Testing; using osu.Game.Database; @@ -19,8 +20,8 @@ namespace osu.Game.Models public RealmNamedFileUsage(RealmFile file, string filename) { - File = file; - Filename = filename; + File = file ?? throw new ArgumentNullException(nameof(file)); + Filename = filename ?? throw new ArgumentNullException(nameof(filename)); } [UsedImplicitly] From 10934450cc2e35ccd02a70278ec073c433057881 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 16:02:46 +0900 Subject: [PATCH 236/363] Fix directory cleanup occurring too early during realm tests --- .../NonVisual/CustomDataDirectoryTest.cs | 47 +++++++------------ 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index a803974d30..216bd0fd3c 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -44,8 +44,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestCustomDirectory() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { using (var storageConfig = new StorageConfigManager(host.InitialStorage)) @@ -63,7 +62,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -71,8 +69,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestSubDirectoryLookup() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { using (var storageConfig = new StorageConfigManager(host.InitialStorage)) @@ -97,7 +94,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -105,8 +101,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigration() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -173,7 +168,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -181,9 +175,8 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationBetweenTwoTargets() { - string customPath = prepareCustomPath(); - string customPath2 = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) + using (prepareCustomPath(out string customPath2)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -205,8 +198,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); - cleanupPath(customPath2); } } } @@ -214,8 +205,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationToSameTargetFails() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -228,7 +218,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -236,9 +225,8 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationFailsOnExistingData() { - string customPath = prepareCustomPath(); - string customPath2 = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) + using (prepareCustomPath(out string customPath2)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -267,8 +255,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); - cleanupPath(customPath2); } } } @@ -276,8 +262,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationToNestedTargetFails() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -298,7 +283,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -306,8 +290,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationToSeeminglyNestedTarget() { - string customPath = prepareCustomPath(); - + using (prepareCustomPath(out string customPath)) using (var host = new CustomTestHeadlessGameHost()) { try @@ -328,7 +311,6 @@ namespace osu.Game.Tests.NonVisual finally { host.Exit(); - cleanupPath(customPath); } } } @@ -343,14 +325,17 @@ namespace osu.Game.Tests.NonVisual return path; } - private static string prepareCustomPath() => Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path-{Guid.NewGuid()}"); + private static IDisposable prepareCustomPath(out string path) + { + path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, $"custom-path-{Guid.NewGuid()}"); + return new InvokeOnDisposal(path, cleanupPath); + } private static void cleanupPath(string path) { try { - if (Directory.Exists(path)) - Directory.Delete(path, true); + if (Directory.Exists(path)) Directory.Delete(path, true); } catch { From 82134ad1a668bb5a35b7d3feae7404090bce4bf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 16:46:28 +0900 Subject: [PATCH 237/363] Allow `null` parameter to `GetWorkingBeatmap` for now --- osu.Game/Beatmaps/BeatmapManager.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 26f538f65b..5f83bbb0dd 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -436,20 +436,23 @@ namespace osu.Game.Beatmaps /// The beatmap to lookup. /// Whether to force a refetch from the database to ensure is up-to-date. /// A instance correlating to the provided . - public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, bool refetch = false) + public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? beatmapInfo, bool refetch = false) { - // Detached sets don't come with files. - // If we seem to be missing files, now is a good time to re-fetch. - if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0) + if (beatmapInfo != null) { - workingBeatmapCache.Invalidate(beatmapInfo); + // Detached sets don't come with files. + // If we seem to be missing files, now is a good time to re-fetch. + if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0) + { + workingBeatmapCache.Invalidate(beatmapInfo); - Guid id = beatmapInfo.ID; - beatmapInfo = Realm.Run(r => r.Find(id)?.Detach()) ?? beatmapInfo; + Guid id = beatmapInfo.ID; + beatmapInfo = Realm.Run(r => r.Find(id)?.Detach()) ?? beatmapInfo; + } + + Debug.Assert(beatmapInfo.IsManaged != true); } - Debug.Assert(beatmapInfo.IsManaged != true); - return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo); } From e34c2f0acac243ecc4a53cc256b02468b032f97e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 16:47:26 +0900 Subject: [PATCH 238/363] Remove unnecessary `nullable-enable` --- osu.Game/Beatmaps/BeatmapUpdater.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index caef094701..62a6fc7293 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable enable - using System.Diagnostics; using System.Linq; using System.Threading.Tasks; From ef42c6ecdf95f659faf31980c2c27e7ebc3a1a94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 16:51:31 +0900 Subject: [PATCH 239/363] Add missing xmldoc --- osu.Game/Beatmaps/IWorkingBeatmapCache.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs index 36c53fce99..e28f802e78 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs @@ -28,6 +28,9 @@ namespace osu.Game.Beatmaps /// The beatmap info to invalidate any cached entries for. void Invalidate(BeatmapInfo beatmapInfo); + /// + /// Fired whenever a is invalidated. + /// event Action OnInvalidated; } } From 0698471627b384e63af9cfd4e47a7f28d08abd6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 17:03:19 +0900 Subject: [PATCH 240/363] Move `BeatmapOnlineLookupQueue` to inside `BeatmapUpdater` --- osu.Game/Beatmaps/BeatmapManager.cs | 6 ++---- osu.Game/Beatmaps/BeatmapUpdater.cs | 21 ++++++++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5f83bbb0dd..e16a87eb50 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -41,7 +41,6 @@ namespace osu.Game.Beatmaps private readonly BeatmapImporter beatmapImporter; private readonly WorkingBeatmapCache workingBeatmapCache; - private readonly BeatmapOnlineLookupQueue? onlineBeatmapLookupQueue; private readonly BeatmapUpdater? beatmapUpdater; public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, @@ -56,8 +55,7 @@ namespace osu.Game.Beatmaps if (difficultyCache == null) throw new ArgumentNullException(nameof(difficultyCache), "Difficulty cache must be provided if online lookups are required."); - onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); - beatmapUpdater = new BeatmapUpdater(this, onlineBeatmapLookupQueue, difficultyCache); + beatmapUpdater = new BeatmapUpdater(this, difficultyCache, api, storage); } var userResources = new RealmFileStore(realm, storage).Store; @@ -474,7 +472,7 @@ namespace osu.Game.Beatmaps public void Dispose() { - onlineBeatmapLookupQueue?.Dispose(); + beatmapUpdater?.Dispose(); } #endregion diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 62a6fc7293..d800b09a2b 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -1,10 +1,14 @@ // 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.Diagnostics; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Online.API; using osu.Game.Rulesets.Objects; using Realms; @@ -13,17 +17,18 @@ namespace osu.Game.Beatmaps /// /// Handles all processing required to ensure a local beatmap is in a consistent state with any changes. /// - public class BeatmapUpdater + public class BeatmapUpdater : IDisposable { private readonly IWorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue onlineLookupQueue; private readonly BeatmapDifficultyCache difficultyCache; - public BeatmapUpdater(IWorkingBeatmapCache workingBeatmapCache, BeatmapOnlineLookupQueue onlineLookupQueue, BeatmapDifficultyCache difficultyCache) + public BeatmapUpdater(IWorkingBeatmapCache workingBeatmapCache, BeatmapDifficultyCache difficultyCache, IAPIProvider api, Storage storage) { this.workingBeatmapCache = workingBeatmapCache; - this.onlineLookupQueue = onlineLookupQueue; this.difficultyCache = difficultyCache; + + onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); } /// @@ -81,5 +86,15 @@ namespace osu.Game.Beatmaps return endTime - startTime; } + + #region Implementation of IDisposable + + public void Dispose() + { + if (onlineLookupQueue.IsNotNull()) + onlineLookupQueue.Dispose(); + } + + #endregion } } From aab4dcefbd060b514c79765ceee8e46ad321515e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 17:13:26 +0900 Subject: [PATCH 241/363] Remove unnecessary invalidation handling flow --- osu.Game/Beatmaps/IWorkingBeatmapCache.cs | 9 --------- osu.Game/Screens/Select/SongSelect.cs | 18 ------------------ 2 files changed, 27 deletions(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs index e28f802e78..3eb33f10d6 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs @@ -1,10 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using System; - namespace osu.Game.Beatmaps { public interface IWorkingBeatmapCache @@ -27,10 +23,5 @@ namespace osu.Game.Beatmaps /// /// The beatmap info to invalidate any cached entries for. void Invalidate(BeatmapInfo beatmapInfo); - - /// - /// Fired whenever a is invalidated. - /// - event Action OnInvalidated; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 73bcabba31..7abcbfca42 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -296,24 +296,8 @@ namespace osu.Game.Screens.Select base.LoadComplete(); modSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(ModSelect); - - beatmaps.OnInvalidated += workingBeatmapInvalidated; } - private void workingBeatmapInvalidated(WorkingBeatmap working) => Scheduler.AddOnce(w => - { - // The global beatmap may have already been updated (ie. by the editor). - // Only perform the actual switch if we still need to. - if (w == Beatmap.Value) - { - // Not sure if this refresh is required. - var beatmapInfo = beatmaps.QueryBeatmap(b => b.ID == w.BeatmapInfo.ID); - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); - } - - updateComponentFromBeatmap(Beatmap.Value); - }, working); - /// /// Creates the buttons to be displayed in the footer. /// @@ -719,8 +703,6 @@ namespace osu.Game.Screens.Select music.TrackChanged -= ensureTrackLooping; modSelectOverlayRegistration?.Dispose(); - - beatmaps.OnInvalidated -= workingBeatmapInvalidated; } /// From 2a7321086548a6f99a35c4517357ff77d3ab996c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 17:17:06 +0900 Subject: [PATCH 242/363] Add xmldoc and update parameter naming for `MemoryCachingComponent.Invalidate` flow --- osu.Game/Database/MemoryCachingComponent.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/MemoryCachingComponent.cs b/osu.Game/Database/MemoryCachingComponent.cs index 215050460b..104943bbae 100644 --- a/osu.Game/Database/MemoryCachingComponent.cs +++ b/osu.Game/Database/MemoryCachingComponent.cs @@ -40,11 +40,15 @@ namespace osu.Game.Database return computed; } - protected void Invalidate(Func invalidationFunction) + /// + /// Invalidate all entries matching a provided predicate. + /// + /// The predicate to decide which keys should be invalidated. + protected void Invalidate(Func matchKeyPredicate) { foreach (var kvp in cache) { - if (invalidationFunction(kvp.Key)) + if (matchKeyPredicate(kvp.Key)) cache.TryRemove(kvp.Key, out _); } } From 7ab83359359602f44440ef56ff8f2968ec1a29fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 17:42:44 +0900 Subject: [PATCH 243/363] Fix various multiplayer test failures due to not waiting for ongoing operation As seen at https://teamcity.ppy.sh/buildConfiguration/Osu_Build/1138?hideProblemsFromDependencies=false&hideTestsFromDependencies=false&expandBuildChangesSection=true&expandBuildTestsSection=true. --- .../Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs | 2 ++ osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs | 2 ++ .../Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 1 + 3 files changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 86da9dc33d..190b2128f9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.Play; @@ -146,6 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer if (mods != null) AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods); + AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => songSelect.AsNonNull().FinaliseSelection(beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index fe584fe3da..e436f418ab 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -104,6 +104,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); BeatmapInfo otherBeatmap = null; + AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); @@ -119,6 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); + AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 285258d7c0..ba79a96692 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -110,6 +110,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() }); + AddUntilStep("wait for ongoing operation to complete", () => !OnlinePlayDependencies.OngoingOperationTracker.InProgress.Value); AddStep("confirm selection", () => songSelect.FinaliseSelection()); AddUntilStep("song select exited", () => !songSelect.IsCurrentScreen()); From 84ecd524c851f6fe0679a7b3b164832a017626fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 17:58:43 +0900 Subject: [PATCH 244/363] Move wait steps upwards --- .../Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 190b2128f9..32fd7724e0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -140,6 +140,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null); AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded); + AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); if (ruleset != null) AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset); @@ -147,7 +148,6 @@ namespace osu.Game.Tests.Visual.Multiplayer if (mods != null) AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods); - AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => songSelect.AsNonNull().FinaliseSelection(beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index ba79a96692..ab4f9c37b2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -107,10 +107,12 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); AddStep("select beatmap", () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == new TaikoRuleset().LegacyID))); + AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); + AddUntilStep("wait for ongoing operation to complete", () => !OnlinePlayDependencies.OngoingOperationTracker.InProgress.Value); + AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() }); - AddUntilStep("wait for ongoing operation to complete", () => !OnlinePlayDependencies.OngoingOperationTracker.InProgress.Value); AddStep("confirm selection", () => songSelect.FinaliseSelection()); AddUntilStep("song select exited", () => !songSelect.IsCurrentScreen()); From e50e0f733975cbc43a2626a1c8aa407a57634a8e Mon Sep 17 00:00:00 2001 From: andy840119 Date: Thu, 30 Jun 2022 23:05:09 +0800 Subject: [PATCH 245/363] Remove the nullable disable annotate. --- osu.Game/Utils/BatteryInfo.cs | 2 -- osu.Game/Utils/ColourUtils.cs | 2 -- osu.Game/Utils/FormatUtils.cs | 2 -- osu.Game/Utils/HumanizerUtils.cs | 2 -- osu.Game/Utils/IDeepCloneable.cs | 2 -- osu.Game/Utils/LegacyRandom.cs | 2 -- osu.Game/Utils/LegacyUtils.cs | 2 -- osu.Game/Utils/NamingUtils.cs | 2 -- osu.Game/Utils/PeriodTracker.cs | 2 -- osu.Game/Utils/StatelessRNG.cs | 2 -- osu.Game/Utils/ZipUtils.cs | 2 -- 11 files changed, 22 deletions(-) diff --git a/osu.Game/Utils/BatteryInfo.cs b/osu.Game/Utils/BatteryInfo.cs index be12671b84..dd9b695e1f 100644 --- a/osu.Game/Utils/BatteryInfo.cs +++ b/osu.Game/Utils/BatteryInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Utils { /// diff --git a/osu.Game/Utils/ColourUtils.cs b/osu.Game/Utils/ColourUtils.cs index 7e665fd9a7..515963971d 100644 --- a/osu.Game/Utils/ColourUtils.cs +++ b/osu.Game/Utils/ColourUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Utils; using osuTK.Graphics; diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index 07a1879267..799dc75ca9 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Humanizer; using osu.Framework.Extensions.LocalisationExtensions; diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs index 27d3317b80..5b7c3630d9 100644 --- a/osu.Game/Utils/HumanizerUtils.cs +++ b/osu.Game/Utils/HumanizerUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Globalization; using Humanizer; diff --git a/osu.Game/Utils/IDeepCloneable.cs b/osu.Game/Utils/IDeepCloneable.cs index a0a2548f6c..6877f346c4 100644 --- a/osu.Game/Utils/IDeepCloneable.cs +++ b/osu.Game/Utils/IDeepCloneable.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Utils { /// A generic interface for a deeply cloneable type. diff --git a/osu.Game/Utils/LegacyRandom.cs b/osu.Game/Utils/LegacyRandom.cs index ace8f8f65c..cf731aa91f 100644 --- a/osu.Game/Utils/LegacyRandom.cs +++ b/osu.Game/Utils/LegacyRandom.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Utils; diff --git a/osu.Game/Utils/LegacyUtils.cs b/osu.Game/Utils/LegacyUtils.cs index 400d3b3865..64306adf50 100644 --- a/osu.Game/Utils/LegacyUtils.cs +++ b/osu.Game/Utils/LegacyUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; diff --git a/osu.Game/Utils/NamingUtils.cs b/osu.Game/Utils/NamingUtils.cs index 6b1be8885d..482e3d0954 100644 --- a/osu.Game/Utils/NamingUtils.cs +++ b/osu.Game/Utils/NamingUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Text.RegularExpressions; diff --git a/osu.Game/Utils/PeriodTracker.cs b/osu.Game/Utils/PeriodTracker.cs index d8251f49ca..ba77702247 100644 --- a/osu.Game/Utils/PeriodTracker.cs +++ b/osu.Game/Utils/PeriodTracker.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; diff --git a/osu.Game/Utils/StatelessRNG.cs b/osu.Game/Utils/StatelessRNG.cs index 548aaa887f..3db632fc42 100644 --- a/osu.Game/Utils/StatelessRNG.cs +++ b/osu.Game/Utils/StatelessRNG.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; namespace osu.Game.Utils diff --git a/osu.Game/Utils/ZipUtils.cs b/osu.Game/Utils/ZipUtils.cs index d6ad3e132e..eb2d2d3b80 100644 --- a/osu.Game/Utils/ZipUtils.cs +++ b/osu.Game/Utils/ZipUtils.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.IO; using SharpCompress.Archives.Zip; From d94f700ab10d6ef1d835a725a83640ca1a569519 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Thu, 30 Jun 2022 23:06:49 +0800 Subject: [PATCH 246/363] Remove the nullable disable annotate in the test project. --- osu.Game.Tests/Utils/NamingUtilsTest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Utils/NamingUtilsTest.cs b/osu.Game.Tests/Utils/NamingUtilsTest.cs index 2195933197..62e688db90 100644 --- a/osu.Game.Tests/Utils/NamingUtilsTest.cs +++ b/osu.Game.Tests/Utils/NamingUtilsTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Game.Utils; From 26de34da844ee45a50db4d2e31a60abf0afd8703 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Thu, 30 Jun 2022 23:20:40 +0800 Subject: [PATCH 247/363] Remove the nullable disable annotation. --- osu.Game/Extensions/CollectionExtensions.cs | 2 -- osu.Game/Extensions/DrawableExtensions.cs | 2 -- osu.Game/Extensions/LanguageExtensions.cs | 2 -- osu.Game/Extensions/TimeDisplayExtensions.cs | 2 -- osu.Game/Extensions/TypeExtensions.cs | 2 -- osu.Game/Extensions/WebRequestExtensions.cs | 2 -- 6 files changed, 12 deletions(-) diff --git a/osu.Game/Extensions/CollectionExtensions.cs b/osu.Game/Extensions/CollectionExtensions.cs index c573d169f1..473dc4b8f4 100644 --- a/osu.Game/Extensions/CollectionExtensions.cs +++ b/osu.Game/Extensions/CollectionExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; namespace osu.Game.Extensions diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index c2c00e342b..d1aba2bfe3 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using Humanizer; using osu.Framework.Bindables; using osu.Framework.Graphics; diff --git a/osu.Game/Extensions/LanguageExtensions.cs b/osu.Game/Extensions/LanguageExtensions.cs index 3b1e9c7719..b67e7fb6fc 100644 --- a/osu.Game/Extensions/LanguageExtensions.cs +++ b/osu.Game/Extensions/LanguageExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Globalization; using osu.Game.Localisation; diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index 94871233ed..98633958ee 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Humanizer; using osu.Framework.Extensions.LocalisationExtensions; diff --git a/osu.Game/Extensions/TypeExtensions.cs b/osu.Game/Extensions/TypeExtensions.cs index 6f160b0479..2e93c81758 100644 --- a/osu.Game/Extensions/TypeExtensions.cs +++ b/osu.Game/Extensions/TypeExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; diff --git a/osu.Game/Extensions/WebRequestExtensions.cs b/osu.Game/Extensions/WebRequestExtensions.cs index 79115c6023..50837a648d 100644 --- a/osu.Game/Extensions/WebRequestExtensions.cs +++ b/osu.Game/Extensions/WebRequestExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Globalization; using Newtonsoft.Json.Linq; using osu.Framework.IO.Network; From a5b1f1a688fe3d9ad0bd2e87b89f45e3c403ad6a Mon Sep 17 00:00:00 2001 From: andy840119 Date: Thu, 30 Jun 2022 23:23:14 +0800 Subject: [PATCH 248/363] mark the string as nullable. --- osu.Game/Extensions/TypeExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Extensions/TypeExtensions.cs b/osu.Game/Extensions/TypeExtensions.cs index 2e93c81758..072b18b0ba 100644 --- a/osu.Game/Extensions/TypeExtensions.cs +++ b/osu.Game/Extensions/TypeExtensions.cs @@ -21,7 +21,7 @@ namespace osu.Game.Extensions /// internal static string GetInvariantInstantiationInfo(this Type type) { - string assemblyQualifiedName = type.AssemblyQualifiedName; + string? assemblyQualifiedName = type.AssemblyQualifiedName; if (assemblyQualifiedName == null) throw new ArgumentException($"{type}'s assembly-qualified name is null. Ensure that it is a concrete type and not a generic type parameter.", nameof(type)); From 48047f2e58f103de50213d4be12c4abd33c47abf Mon Sep 17 00:00:00 2001 From: andy840119 Date: Thu, 30 Jun 2022 23:29:49 +0800 Subject: [PATCH 249/363] Move the null check in the outside. AddCursor() should not accept the null value. --- osu.Game/Extensions/WebRequestExtensions.cs | 2 +- osu.Game/Online/API/Requests/GetNewsRequest.cs | 10 +++++----- .../Online/API/Requests/SearchBeatmapSetsRequest.cs | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Extensions/WebRequestExtensions.cs b/osu.Game/Extensions/WebRequestExtensions.cs index 50837a648d..a80b79f259 100644 --- a/osu.Game/Extensions/WebRequestExtensions.cs +++ b/osu.Game/Extensions/WebRequestExtensions.cs @@ -16,7 +16,7 @@ namespace osu.Game.Extensions /// public static void AddCursor(this WebRequest webRequest, Cursor cursor) { - cursor?.Properties.ForEach(x => + cursor.Properties.ForEach(x => { webRequest.AddParameter("cursor[" + x.Key + "]", (x.Value as JValue)?.ToString(CultureInfo.InvariantCulture) ?? x.Value.ToString()); }); diff --git a/osu.Game/Online/API/Requests/GetNewsRequest.cs b/osu.Game/Online/API/Requests/GetNewsRequest.cs index e1c9eefe30..64bed344bb 100644 --- a/osu.Game/Online/API/Requests/GetNewsRequest.cs +++ b/osu.Game/Online/API/Requests/GetNewsRequest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.IO.Network; using osu.Game.Extensions; @@ -11,9 +9,9 @@ namespace osu.Game.Online.API.Requests public class GetNewsRequest : APIRequest { private readonly int? year; - private readonly Cursor cursor; + private readonly Cursor? cursor; - public GetNewsRequest(int? year = null, Cursor cursor = null) + public GetNewsRequest(int? year = null, Cursor? cursor = null) { this.year = year; this.cursor = cursor; @@ -22,7 +20,9 @@ namespace osu.Game.Online.API.Requests protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); - req.AddCursor(cursor); + + if (cursor != null) + req.AddCursor(cursor); if (year.HasValue) req.AddParameter("year", year.Value.ToString()); diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 73f6fce4f9..082f9bb371 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -112,7 +112,8 @@ namespace osu.Game.Online.API.Requests req.AddParameter("nsfw", ExplicitContent == SearchExplicit.Show ? "true" : "false"); - req.AddCursor(cursor); + if (cursor != null) + req.AddCursor(cursor); return req; } From 3785027284ba1701af5b001d4dadd73bd96145d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 14:35:47 +0900 Subject: [PATCH 250/363] Update `OsuSliderBar` colours to not use transparency --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 3356153e17..493c8638cc 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -111,7 +111,6 @@ namespace osu.Game.Graphics.UserInterface RelativeSizeAxes = Axes.None, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Alpha = 0.5f, }, }, }, @@ -137,7 +136,7 @@ namespace osu.Game.Graphics.UserInterface { sample = audio.Samples.Get(@"UI/notch-tick"); AccentColour = colourProvider?.Highlight1 ?? colours.Pink; - BackgroundColour = colourProvider?.Background5 ?? colours.Pink.Opacity(0.5f); + BackgroundColour = colourProvider?.Background5 ?? colours.PinkDarker.Darken(1); } protected override void Update() From d54f7fc7289124ca119ac71df2b6cde2f621c392 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 19:54:08 +0900 Subject: [PATCH 251/363] Move slider range control to song select --- .../Graphics/UserInterface/OsuSliderBar.cs | 16 ++-- .../UserInterface/SongSelectSettings.cs | 36 -------- .../Select/DifficultyRangeFilterControl.cs | 90 +++++++++++++++++++ osu.Game/Screens/Select/FilterControl.cs | 5 ++ 4 files changed, 103 insertions(+), 44 deletions(-) create mode 100644 osu.Game/Screens/Select/DifficultyRangeFilterControl.cs diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 493c8638cc..1e472ed85b 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -38,8 +38,8 @@ namespace osu.Game.Graphics.UserInterface private T lastSampleValue; protected readonly Nub Nub; - private readonly Box leftBox; - private readonly Box rightBox; + protected readonly Box LeftBox; + protected readonly Box RightBox; private readonly Container nubContainer; public virtual LocalisableString TooltipText { get; private set; } @@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterface set { accentColour = value; - leftBox.Colour = value; + LeftBox.Colour = value; } } @@ -69,7 +69,7 @@ namespace osu.Game.Graphics.UserInterface set { backgroundColour = value; - rightBox.Colour = value; + RightBox.Colour = value; } } @@ -96,7 +96,7 @@ namespace osu.Game.Graphics.UserInterface CornerRadius = 5f, Children = new Drawable[] { - leftBox = new Box + LeftBox = new Box { Height = 5, EdgeSmoothness = new Vector2(0, 0.5f), @@ -104,7 +104,7 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - rightBox = new Box + RightBox = new Box { Height = 5, EdgeSmoothness = new Vector2(0, 0.5f), @@ -225,9 +225,9 @@ namespace osu.Game.Graphics.UserInterface protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - leftBox.Scale = new Vector2(Math.Clamp( + LeftBox.Scale = new Vector2(Math.Clamp( RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); - rightBox.Scale = new Vector2(Math.Clamp( + RightBox.Scale = new Vector2(Math.Clamp( DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, DrawWidth), 1); } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 507e116723..708bee6fbd 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -3,13 +3,10 @@ #nullable disable -using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays.Mods.Input; @@ -17,20 +14,11 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface { public class SongSelectSettings : SettingsSubsection { - private Bindable minStars; - private Bindable maxStars; - protected override LocalisableString Header => UserInterfaceStrings.SongSelectHeader; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - minStars = config.GetBindable(OsuSetting.DisplayStarsMinimum); - maxStars = config.GetBindable(OsuSetting.DisplayStarsMaximum); - - minStars.ValueChanged += min => maxStars.Value = Math.Max(min.NewValue, maxStars.Value); - maxStars.ValueChanged += max => minStars.Value = Math.Min(max.NewValue, minStars.Value); - Children = new Drawable[] { new SettingsCheckbox @@ -44,20 +32,6 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = UserInterfaceStrings.ShowConvertedBeatmaps, Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), }, - new SettingsSlider - { - LabelText = UserInterfaceStrings.StarsMinimum, - Current = config.GetBindable(OsuSetting.DisplayStarsMinimum), - KeyboardStep = 0.1f, - Keywords = new[] { "minimum", "maximum", "star", "difficulty" } - }, - new SettingsSlider - { - LabelText = UserInterfaceStrings.StarsMaximum, - Current = config.GetBindable(OsuSetting.DisplayStarsMaximum), - KeyboardStep = 0.1f, - Keywords = new[] { "minimum", "maximum", "star", "difficulty" } - }, new SettingsEnumDropdown { LabelText = UserInterfaceStrings.RandomSelectionAlgorithm, @@ -71,15 +45,5 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface } }; } - - private class MaximumStarsSlider : StarsSlider - { - public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; - } - - private class StarsSlider : OsuSliderBar - { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); - } } } diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs new file mode 100644 index 0000000000..6148d22838 --- /dev/null +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; +using osuTK; + +namespace osu.Game.Screens.Select +{ + internal class DifficultyRangeFilterControl : CompositeDrawable + { + private Bindable minStars; + private Bindable maxStars; + + private StarsSlider lowerSlider; + private MaximumStarsSlider upperSlider; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + const float vertical_offset = 15; + + InternalChildren = new[] + { + new OsuSpriteText + { + Text = "Difficulty range", + Font = OsuFont.GetFont(size: 14), + }, + upperSlider = new MaximumStarsSlider + { + Current = config.GetBindable(OsuSetting.DisplayStarsMaximum), + KeyboardStep = 0.1f, + RelativeSizeAxes = Axes.X, + Y = vertical_offset, + }, + lowerSlider = new StarsSlider + { + Current = config.GetBindable(OsuSetting.DisplayStarsMinimum), + KeyboardStep = 0.1f, + RelativeSizeAxes = Axes.X, + Y = vertical_offset, + }, + lowerSlider.Nub.CreateProxy(), + upperSlider.Nub.CreateProxy(), + }; + + lowerSlider.LeftBox.Height = 6; + + minStars = config.GetBindable(OsuSetting.DisplayStarsMinimum); + maxStars = config.GetBindable(OsuSetting.DisplayStarsMaximum); + + lowerSlider.AccentColour = lowerSlider.BackgroundColour; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + minStars.ValueChanged += min => maxStars.Value = Math.Max(min.NewValue, maxStars.Value); + maxStars.ValueChanged += max => minStars.Value = Math.Min(max.NewValue, minStars.Value); + } + + private class MaximumStarsSlider : StarsSlider + { + public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; + } + + private class StarsSlider : OsuSliderBar + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Nub.ReceivePositionalInputAt(screenSpacePos); + + public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); + + public new Nub Nub => base.Nub; + public new Box LeftBox => base.LeftBox; + } + } +} diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index da764223a3..714de2487f 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -158,6 +158,11 @@ namespace osu.Game.Screens.Select Height = 20, Children = new Drawable[] { + new DifficultyRangeFilterControl + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + }, collectionDropdown = new CollectionFilterDropdown { Anchor = Anchor.TopRight, From 32139ac13fe90f00123d77d42b9d300afccd3746 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 14:22:15 +0900 Subject: [PATCH 252/363] Tidy up implementation and add basic visual test --- .../TestSceneDifficultyRangeFilterControl.cs | 26 ++++++++ .../Select/DifficultyRangeFilterControl.cs | 61 ++++++++++++++----- 2 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs new file mode 100644 index 0000000000..cc0909a835 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Screens.Select; +using osuTK; + +namespace osu.Game.Tests.Visual.SongSelect +{ + public class TestSceneDifficultyRangeFilterControl : OsuTestScene + { + [Test] + public void TestBasic() + { + Child = new DifficultyRangeFilterControl + { + Width = 200, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(3), + }; + } + } +} diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index 6148d22838..156e373b6d 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics; @@ -15,13 +14,14 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select { internal class DifficultyRangeFilterControl : CompositeDrawable { - private Bindable minStars; - private Bindable maxStars; + private Bindable lowerStars; + private Bindable upperStars; private StarsSlider lowerSlider; private MaximumStarsSlider upperSlider; @@ -45,46 +45,75 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.X, Y = vertical_offset, }, - lowerSlider = new StarsSlider + lowerSlider = new MinimumStarsSlider { Current = config.GetBindable(OsuSetting.DisplayStarsMinimum), KeyboardStep = 0.1f, RelativeSizeAxes = Axes.X, Y = vertical_offset, }, - lowerSlider.Nub.CreateProxy(), upperSlider.Nub.CreateProxy(), + lowerSlider.Nub.CreateProxy(), }; - lowerSlider.LeftBox.Height = 6; - - minStars = config.GetBindable(OsuSetting.DisplayStarsMinimum); - maxStars = config.GetBindable(OsuSetting.DisplayStarsMaximum); - - lowerSlider.AccentColour = lowerSlider.BackgroundColour; + lowerStars = config.GetBindable(OsuSetting.DisplayStarsMinimum); + upperStars = config.GetBindable(OsuSetting.DisplayStarsMaximum); } protected override void LoadComplete() { base.LoadComplete(); - minStars.ValueChanged += min => maxStars.Value = Math.Max(min.NewValue, maxStars.Value); - maxStars.ValueChanged += max => minStars.Value = Math.Min(max.NewValue, minStars.Value); + lowerStars.ValueChanged += min => upperStars.Value = Math.Max(min.NewValue + 0.1, upperStars.Value); + upperStars.ValueChanged += max => lowerStars.Value = Math.Min(max.NewValue - 0.1, lowerStars.Value); + } + + private class MinimumStarsSlider : StarsSlider + { + protected override void LoadComplete() + { + base.LoadComplete(); + + LeftBox.Height = 6; // hide any colour bleeding from overlap + + AccentColour = BackgroundColour; + BackgroundColour = Color4.Transparent; + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + base.ReceivePositionalInputAt(screenSpacePos) + && screenSpacePos.X <= Nub.ScreenSpaceDrawQuad.TopRight.X; + + public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; } private class MaximumStarsSlider : StarsSlider { + protected override void LoadComplete() + { + base.LoadComplete(); + + RightBox.Height = 6; // just to match the left bar height really + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + base.ReceivePositionalInputAt(screenSpacePos) + && screenSpacePos.X >= Nub.ScreenSpaceDrawQuad.TopLeft.X; public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; } private class StarsSlider : OsuSliderBar { - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Nub.ReceivePositionalInputAt(screenSpacePos); - public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); public new Nub Nub => base.Nub; - public new Box LeftBox => base.LeftBox; + + protected override void LoadComplete() + { + base.LoadComplete(); + Nub.Width = Nub.HEIGHT; + RangePadding = Nub.Width / 2; + } } } } From cb9947b1c92ac11b7735074044b6b5c2f9afdaa4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 14:41:32 +0900 Subject: [PATCH 253/363] Align song select components a bit better --- osu.Game/Screens/Select/DifficultyRangeFilterControl.cs | 2 +- osu.Game/Screens/Select/FilterControl.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index 156e373b6d..8e67a669e4 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - const float vertical_offset = 15; + const float vertical_offset = 13; InternalChildren = new[] { diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 714de2487f..a92b631100 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -155,20 +155,23 @@ namespace osu.Game.Screens.Select new Container { RelativeSizeAxes = Axes.X, - Height = 20, + Height = 40, Children = new Drawable[] { new DifficultyRangeFilterControl { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, RelativeSizeAxes = Axes.Both, - Width = 0.5f, + Width = 0.48f, }, collectionDropdown = new CollectionFilterDropdown { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, RelativeSizeAxes = Axes.X, - Width = 0.4f, + Y = 4, + Width = 0.5f, } } }, From 545df0a8e8d8d0ac75db6060266afbf998774597 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 15:17:26 +0900 Subject: [PATCH 254/363] Display difficulty on nub --- osu.Game/Graphics/UserInterface/Nub.cs | 2 +- .../Select/DifficultyRangeFilterControl.cs | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 249fa2fbb2..7a3e54ddf1 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -19,7 +19,7 @@ using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterface { - public class Nub : CompositeDrawable, IHasCurrentValue, IHasAccentColour + public class Nub : Container, IHasCurrentValue, IHasAccentColour { public const float HEIGHT = 15; diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index 8e67a669e4..7115c59ebf 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -83,8 +83,6 @@ namespace osu.Game.Screens.Select public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && screenSpacePos.X <= Nub.ScreenSpaceDrawQuad.TopRight.X; - - public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; } private class MaximumStarsSlider : StarsSlider @@ -99,12 +97,15 @@ namespace osu.Game.Screens.Select public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && screenSpacePos.X >= Nub.ScreenSpaceDrawQuad.TopLeft.X; - public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; } private class StarsSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); + private OsuSpriteText currentDisplay; + + public override LocalisableString TooltipText => Current.IsDefault + ? UserInterfaceStrings.NoLimit + : Current.Value.ToString(@"0.## stars"); public new Nub Nub => base.Nub; @@ -113,6 +114,20 @@ namespace osu.Game.Screens.Select base.LoadComplete(); Nub.Width = Nub.HEIGHT; RangePadding = Nub.Width / 2; + + Nub.Add(currentDisplay = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -0.5f, + Colour = Color4.White, + Font = OsuFont.Torus.With(size: 10), + }); + + Current.BindValueChanged(current => + { + currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : "∞"; + }, true); } } } From f3af61213317ff8c735157bc0936fbf84d3ba9e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 15:17:53 +0900 Subject: [PATCH 255/363] Suggest removing difficulty filter if no matches found at song select --- .../Screens/Select/NoResultsPlaceholder.cs | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index e1a8e3e060..d018228e14 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Select Masking = true; CornerRadius = 10; - Width = 300; + Width = 400; AutoSizeAxes = Axes.Y; Anchor = Anchor.Centre; @@ -118,22 +118,35 @@ namespace osu.Game.Screens.Select textFlow.AddParagraph("No beatmaps match your filter criteria!"); textFlow.AddParagraph(string.Empty); - if (string.IsNullOrEmpty(filter?.SearchText)) + if (filter?.UserStarDifficulty.HasFilter == true) { - // TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch). - // TODO: Make this message more certain by ensuring the osu! beatmaps exist before suggesting. - if (filter?.Ruleset.OnlineID > 0 && !filter.AllowConvertedBeatmaps) + textFlow.AddParagraph("- Try "); + textFlow.AddLink("removing", () => { - textFlow.AddParagraph("Beatmaps may be available by "); - textFlow.AddLink("enabling automatic conversion", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); - textFlow.AddText("!"); - } + config.SetValue(OsuSetting.DisplayStarsMinimum, 0.0); + config.SetValue(OsuSetting.DisplayStarsMaximum, 10.1); + }); + + string lowerStar = filter.UserStarDifficulty.Min == null ? "∞" : $"{filter.UserStarDifficulty.Min:N1}"; + string upperStar = filter.UserStarDifficulty.Max == null ? "∞" : $"{filter.UserStarDifficulty.Max:N1}"; + + textFlow.AddText($" the {lowerStar}-{upperStar} star difficulty filter."); } - else + + // TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch). + // TODO: Make this message more certain by ensuring the osu! beatmaps exist before suggesting. + if (filter?.Ruleset.OnlineID > 0 && !filter.AllowConvertedBeatmaps) { - textFlow.AddParagraph("You can try "); + textFlow.AddParagraph("- Try"); + textFlow.AddLink(" enabling ", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); + textFlow.AddText("automatic conversion!"); + } + + if (!string.IsNullOrEmpty(filter?.SearchText)) + { + textFlow.AddParagraph("- Try "); textFlow.AddLink("searching online", LinkAction.SearchBeatmapSet, filter.SearchText); - textFlow.AddText(" for this query."); + textFlow.AddText($" for \"{filter.SearchText}\"."); } } From 3e0f4e7609106115905452202cdc6c538f07a3ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 15:28:55 +0900 Subject: [PATCH 256/363] Add test coverage of difficulty filter reset --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 16 ++++++++++++++++ osu.Game/Screens/Select/NoResultsPlaceholder.cs | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6896b442e0..159a3b1923 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -97,6 +97,22 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); } + [Test] + public void TestPlaceholderStarDifficulty() + { + addRulesetImportStep(0); + AddStep("change star filter", () => config.SetValue(OsuSetting.DisplayStarsMinimum, 10.0)); + + createSongSelect(); + + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); + + AddStep("click link in placeholder", () => getPlaceholder().ChildrenOfType().First().TriggerClick()); + + AddUntilStep("star filter reset", () => config.Get(OsuSetting.DisplayStarsMinimum) == 0.0); + AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Hidden); + } + [Test] public void TestPlaceholderConvertSetting() { diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index d018228e14..5d5eafd2e6 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Select // TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch). // TODO: Make this message more certain by ensuring the osu! beatmaps exist before suggesting. - if (filter?.Ruleset.OnlineID > 0 && !filter.AllowConvertedBeatmaps) + if (filter?.Ruleset?.OnlineID > 0 && !filter.AllowConvertedBeatmaps) { textFlow.AddParagraph("- Try"); textFlow.AddLink(" enabling ", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); From 58e25a3a01369996401c1ed931667e19bf6d85b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 15:42:35 +0900 Subject: [PATCH 257/363] Fix potential crash in `BeatmapLeaderboard` during rapid refresh operations --- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index f1f11518ee..43eaff56b3 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -111,7 +112,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { - subscribeToLocalScores(cancellationToken); + subscribeToLocalScores(fetchBeatmapInfo, cancellationToken); return null; } @@ -174,14 +175,13 @@ namespace osu.Game.Screens.Select.Leaderboards Action = () => ScoreSelected?.Invoke(model) }; - private void subscribeToLocalScores(CancellationToken cancellationToken) + private void subscribeToLocalScores(BeatmapInfo beatmapInfo, CancellationToken cancellationToken) { + Debug.Assert(beatmapInfo != null); + scoreSubscription?.Dispose(); scoreSubscription = null; - if (beatmapInfo == null) - return; - scoreSubscription = realm.RegisterForNotifications(r => r.All().Filter($"{nameof(ScoreInfo.BeatmapInfo)}.{nameof(BeatmapInfo.ID)} == $0" + $" AND {nameof(ScoreInfo.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $1" From 1777a6d24abffb933105e6fdd148368a4c841d50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 16:06:18 +0900 Subject: [PATCH 258/363] Add attribute to retry flaky tests on normal CI runs --- osu.Game/Tests/FlakyTestAttribute.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 osu.Game/Tests/FlakyTestAttribute.cs diff --git a/osu.Game/Tests/FlakyTestAttribute.cs b/osu.Game/Tests/FlakyTestAttribute.cs new file mode 100644 index 0000000000..a5646f5d00 --- /dev/null +++ b/osu.Game/Tests/FlakyTestAttribute.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System; +using NUnit.Framework; + +namespace osu.Game.Tests +{ + /// + /// An attribute to mark any flaky tests. + /// Will add a retry count unless environment variable `FAIL_FLAKY_TESTS` is set to `1`. + /// + public class FlakyTestAttribute : RetryAttribute + { + public FlakyTestAttribute() + : this(10) + { + } + + public FlakyTestAttribute(int tryCount) + : base(Environment.GetEnvironmentVariable("FAIL_FLAKY_TESTS") == "1" ? 0 : tryCount) + { + } + } +} From b597843579c3a10fbf043b91bac12b1db3ac7b47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 16:17:40 +0900 Subject: [PATCH 259/363] Mark and document remaining flaky tests --- .../TestSceneSliderSnaking.cs | 21 ++++++++++ .../TestSceneMasterGameplayClockContainer.cs | 10 +++++ .../Editing/TestSceneEditorBeatmapCreation.cs | 19 ++++++++++ .../Visual/Editing/TestSceneTimelineZoom.cs | 10 +++++ .../TestSceneCompletionCancellation.cs | 10 +++++ .../TestSceneMultiplayerMatchSubScreen.cs | 38 +++++++++++++++++++ 6 files changed, 108 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index 366793058d..0118ed6513 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Storyboards; +using osu.Game.Tests; using osuTK; namespace osu.Game.Rulesets.Osu.Tests @@ -71,6 +72,16 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(0)] [TestCase(1)] [TestCase(2)] + [FlakyTest] + /* + * Fail rate around 0.15% + * + * TearDown : System.TimeoutException : "wait for seek to finish" timed out + * --TearDown + * at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() + * at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) + * at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) + */ public void TestSnakingEnabled(int sliderIndex) { AddStep("enable autoplay", () => autoplay = true); @@ -95,6 +106,16 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(0)] [TestCase(1)] [TestCase(2)] + [FlakyTest] + /* + * Fail rate around 0.15% + * + * TearDown : System.TimeoutException : "wait for seek to finish" timed out + * --TearDown + * at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() + * at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) + * at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) + */ public void TestSnakingDisabled(int sliderIndex) { AddStep("have autoplay", () => autoplay = true); diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index ced397921c..ae431e77ae 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -78,6 +78,16 @@ namespace osu.Game.Tests.Gameplay } [Test] + [FlakyTest] + /* + * Fail rate around 0.15% + * + * TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : gameplay clock time = 2500 + * --TearDown + * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() + * at osu.Framework.Threading.Scheduler.Update() + * at osu.Framework.Graphics.Drawable.UpdateSubTree() + */ public void TestSeekPerformsInGameplayTime( [Values(1.0, 0.5, 2.0)] double clockRate, [Values(0.0, 200.0, -200.0)] double userOffset, diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index b711d55e15..2707682b4c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -82,6 +82,25 @@ namespace osu.Game.Tests.Visual.Editing } [Test] + [FlakyTest] + /* + * Fail rate around 1.2%. + * + * Failing with realm refetch occasionally being null. + * My only guess is that the WorkingBeatmap at SetupScreen is dummy instead of the true one. + * If it's something else, we have larger issues with realm, but I don't think that's the case. + * + * at osu.Framework.Logging.ThrowingTraceListener.Fail(String message1, String message2) + * at System.Diagnostics.TraceInternal.Fail(String message, String detailMessage) + * at System.Diagnostics.TraceInternal.TraceProvider.Fail(String message, String detailMessage) + * at System.Diagnostics.Debug.Fail(String message, String detailMessage) + * at osu.Game.Database.ModelManager`1.<>c__DisplayClass8_0.b__0(Realm realm) ModelManager.cs:line 50 + * at osu.Game.Database.RealmExtensions.Write(Realm realm, Action`1 function) RealmExtensions.cs:line 14 + * at osu.Game.Database.ModelManager`1.performFileOperation(TModel item, Action`1 operation) ModelManager.cs:line 47 + * at osu.Game.Database.ModelManager`1.AddFile(TModel item, Stream contents, String filename) ModelManager.cs:line 37 + * at osu.Game.Screens.Edit.Setup.ResourcesSection.ChangeAudioTrack(FileInfo source) ResourcesSection.cs:line 115 + * at osu.Game.Tests.Visual.Editing.TestSceneEditorBeatmapCreation.b__11_0() TestSceneEditorBeatmapCreation.cs:line 101 + */ public void TestAddAudioTrack() { AddAssert("switch track to real track", () => diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index fd103ff70f..2cada1989e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -14,6 +14,16 @@ namespace osu.Game.Tests.Visual.Editing public override Drawable CreateTestComponent() => Empty(); [Test] + [FlakyTest] + /* + * Fail rate around 0.3% + * + * TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : range halved + * --TearDown + * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() + * at osu.Framework.Threading.Scheduler.Update() + * at osu.Framework.Graphics.Drawable.UpdateSubTree() + */ public void TestVisibleRangeUpdatesOnZoomChange() { double initialVisibleRange = 0; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index 6aedc64370..13ceb05aff 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -61,6 +61,16 @@ namespace osu.Game.Tests.Visual.Gameplay /// Tests whether can still pause after cancelling completion by reverting back to true. /// [Test] + [FlakyTest] + /* + * Fail rate around 0.45% + * + * TearDown : System.TimeoutException : "completion set by processor" timed out + * --TearDown + * at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() + * at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) + * at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) + */ public void TestCanPauseAfterCancellation() { complete(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index bb7587ac56..3a48f68f1d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -74,6 +74,25 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] + /* + * Fail rate around 1.5% + * + * TearDown : System.AggregateException : One or more errors occurred. (Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')) + ----> System.ArgumentOutOfRangeException : Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') + * --TearDown + * at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) + * at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) + * at osu.Framework.Extensions.TaskExtensions.WaitSafely(Task task) + * at osu.Framework.Testing.TestScene.checkForErrors() + * at osu.Framework.Testing.TestScene.RunTestsFromNUnit() + *--ArgumentOutOfRangeException + * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) + * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) + * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller) + * at osu.Game.Online.Multiplayer.MultiplayerClient.<>c__DisplayClass106_0.b__0() in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Online\Multiplayer\MultiplayerClient .cs:line 702 + * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() + */ public void TestCreatedRoom() { AddStep("add playlist item", () => @@ -90,6 +109,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestTaikoOnlyMod() { AddStep("add playlist item", () => @@ -110,6 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestSettingValidity() { AddAssert("create button not enabled", () => !this.ChildrenOfType().Single().Enabled.Value); @@ -126,6 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestStartMatchWhileSpectating() { AddStep("set playlist", () => @@ -156,6 +178,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestFreeModSelectionHasAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -183,6 +206,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestModSelectKeyWithAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -204,6 +228,19 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] + /* + * Fail rate around 0.3% + * + * Somehow there are two mod select overlays? + * + * TearDown : System.InvalidOperationException : Sequence contains more than one element + * --TearDown + * at System.Linq.ThrowHelper.ThrowMoreThanOneElementException() + * at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found) + * at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source) + * at osu.Game.Tests.Visual.Multiplayer.TestSceneMultiplayerMatchSubScreen.b__14_3() in /opt/buildagent/work/ecd860037212ac52/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs:line 223 + */ public void TestModSelectKeyWithNoAllowedMods() { AddStep("add playlist item with no allowed mods", () => @@ -224,6 +261,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] + [FlakyTest] public void TestNextPlaylistItemSelectedAfterCompletion() { AddStep("add two playlist items", () => From c4f166084133f129d1f0195473836943db4dff5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 16:20:34 +0900 Subject: [PATCH 260/363] Rename ENVVAR in line with previous one (`OSU_TESTS_NO_TIMEOUT`) --- osu.Game/Tests/FlakyTestAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/FlakyTestAttribute.cs b/osu.Game/Tests/FlakyTestAttribute.cs index a5646f5d00..a1c16284c8 100644 --- a/osu.Game/Tests/FlakyTestAttribute.cs +++ b/osu.Game/Tests/FlakyTestAttribute.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests } public FlakyTestAttribute(int tryCount) - : base(Environment.GetEnvironmentVariable("FAIL_FLAKY_TESTS") == "1" ? 0 : tryCount) + : base(Environment.GetEnvironmentVariable("OSU_TESTS_FAIL_FLAKY") == "1" ? 0 : tryCount) { } } From 615c3234d8df6339635f6ae7dcbca6158b282e26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 16:25:21 +0900 Subject: [PATCH 261/363] Remove non-required NRT hint --- osu.Game/Tests/FlakyTestAttribute.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Tests/FlakyTestAttribute.cs b/osu.Game/Tests/FlakyTestAttribute.cs index a1c16284c8..299dbb89a2 100644 --- a/osu.Game/Tests/FlakyTestAttribute.cs +++ b/osu.Game/Tests/FlakyTestAttribute.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. -#nullable disable using System; using NUnit.Framework; From c22e77e481f62226507a7ef721e1f106b6f1bc4e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 16:40:52 +0900 Subject: [PATCH 262/363] Fix test sometimes referencing old ModSelect object --- .../TestSceneMultiplayerMatchSubScreen.cs | 9 ++++----- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 20 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index bb7587ac56..07a6352e10 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -176,8 +176,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("mod select contents loaded", () => this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded)); AddUntilStep("mod select contains only double time mod", - () => this.ChildrenOfType() - .SingleOrDefault()? + () => this.ChildrenOfType().Single().UserModsSelectOverlay .ChildrenOfType() .SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime); } @@ -200,7 +199,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press toggle mod select key", () => InputManager.Key(Key.F1)); - AddUntilStep("mod select shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + AddUntilStep("mod select shown", () => this.ChildrenOfType().Single().UserModsSelectOverlay.State.Value == Visibility.Visible); } [Test] @@ -219,8 +218,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press toggle mod select key", () => InputManager.Key(Key.F1)); - AddWaitStep("wait some", 3); - AddAssert("mod select not shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + // AddWaitStep("wait some", 3); + AddAssert("mod select not shown", () => this.ChildrenOfType().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden); } [Test] diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index e7d31fbdde..db636f6f95 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public readonly Room Room; private readonly bool allowEdit; - private ModSelectOverlay userModsSelectOverlay; + public ModSelectOverlay UserModsSelectOverlay { get; private set; } [CanBeNull] private IDisposable userModsSelectOverlayRegistration; @@ -236,7 +236,7 @@ namespace osu.Game.Screens.OnlinePlay.Match } }; - LoadComponent(userModsSelectOverlay = new UserModSelectOverlay(OverlayColourScheme.Plum) + LoadComponent(UserModsSelectOverlay = new UserModSelectOverlay(OverlayColourScheme.Plum) { SelectedMods = { BindTarget = UserMods }, IsValidMod = _ => false @@ -269,7 +269,7 @@ namespace osu.Game.Screens.OnlinePlay.Match beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem); beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap()); - userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay); + userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(UserModsSelectOverlay); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -289,9 +289,9 @@ namespace osu.Game.Screens.OnlinePlay.Match return base.OnBackButton(); } - if (userModsSelectOverlay.State.Value == Visibility.Visible) + if (UserModsSelectOverlay.State.Value == Visibility.Visible) { - userModsSelectOverlay.Hide(); + UserModsSelectOverlay.Hide(); return true; } @@ -304,7 +304,7 @@ namespace osu.Game.Screens.OnlinePlay.Match return base.OnBackButton(); } - protected void ShowUserModSelect() => userModsSelectOverlay.Show(); + protected void ShowUserModSelect() => UserModsSelectOverlay.Show(); public override void OnEntering(ScreenTransitionEvent e) { @@ -385,13 +385,13 @@ namespace osu.Game.Screens.OnlinePlay.Match if (!selected.AllowedMods.Any()) { UserModsSection?.Hide(); - userModsSelectOverlay.Hide(); - userModsSelectOverlay.IsValidMod = _ => false; + UserModsSelectOverlay.Hide(); + UserModsSelectOverlay.IsValidMod = _ => false; } else { UserModsSection?.Show(); - userModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType()); + UserModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType()); } } @@ -430,7 +430,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private void onLeaving() { - userModsSelectOverlay.Hide(); + UserModsSelectOverlay.Hide(); endHandlingTrack(); } From 58c9bb031fbbaf958079c1568b4df5cc715bc0ff Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 16:51:15 +0900 Subject: [PATCH 263/363] Apply PR reviews/fixes --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 2 +- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 07a6352e10..f925622a80 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -218,7 +218,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("press toggle mod select key", () => InputManager.Key(Key.F1)); - // AddWaitStep("wait some", 3); + AddWaitStep("wait some", 3); AddAssert("mod select not shown", () => this.ChildrenOfType().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden); } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index db636f6f95..bae25dc9f8 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public readonly Room Room; private readonly bool allowEdit; - public ModSelectOverlay UserModsSelectOverlay { get; private set; } + internal ModSelectOverlay UserModsSelectOverlay { get; private set; } [CanBeNull] private IDisposable userModsSelectOverlayRegistration; From 8f8c2a8c9aa1e87a2468bed6408a33939e6d9b85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 16:55:47 +0900 Subject: [PATCH 264/363] Remove one flaky test description (fixed via #18966) --- .../TestSceneMultiplayerMatchSubScreen.cs | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 3a48f68f1d..75a011b6eb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestTaikoOnlyMod() { AddStep("add playlist item", () => @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestSettingValidity() { AddAssert("create button not enabled", () => !this.ChildrenOfType().Single().Enabled.Value); @@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestStartMatchWhileSpectating() { AddStep("set playlist", () => @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestFreeModSelectionHasAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -206,7 +206,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestModSelectKeyWithAllowedMods() { AddStep("add playlist item with allowed mod", () => @@ -228,19 +228,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] - /* - * Fail rate around 0.3% - * - * Somehow there are two mod select overlays? - * - * TearDown : System.InvalidOperationException : Sequence contains more than one element - * --TearDown - * at System.Linq.ThrowHelper.ThrowMoreThanOneElementException() - * at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found) - * at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source) - * at osu.Game.Tests.Visual.Multiplayer.TestSceneMultiplayerMatchSubScreen.b__14_3() in /opt/buildagent/work/ecd860037212ac52/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs:line 223 - */ + [FlakyTest] // See above public void TestModSelectKeyWithNoAllowedMods() { AddStep("add playlist item with no allowed mods", () => @@ -261,7 +249,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] + [FlakyTest] // See above public void TestNextPlaylistItemSelectedAfterCompletion() { AddStep("add two playlist items", () => From 17ad6648d1e837a7a85f57792a9185377b37bd52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 17:07:00 +0900 Subject: [PATCH 265/363] Fix new test failing on headless runs --- .../TestSceneDifficultyRangeFilterControl.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs index cc0909a835..ffad15287b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs @@ -14,13 +14,16 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestBasic() { - Child = new DifficultyRangeFilterControl + AddStep("create control", () => { - Width = 200, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(3), - }; + Child = new DifficultyRangeFilterControl + { + Width = 200, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(3), + }; + }); } } } From 0e1f08eff870b325b837b03db6cce7f1679f3d20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 17:10:04 +0900 Subject: [PATCH 266/363] Fix regressing `BeatmapRecommendations` tests due to diffcalc running at --- .../TestSceneBeatmapRecommendations.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 117515977e..504ded5406 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -166,15 +167,22 @@ namespace osu.Game.Tests.Visual.SongSelect var beatmapSet = TestResources.CreateTestBeatmapSetInfo(rulesets.Length, rulesets); - for (int i = 0; i < rulesets.Length; i++) + var importedBeatmapSet = Game.BeatmapManager.Import(beatmapSet); + + Debug.Assert(importedBeatmapSet != null); + + importedBeatmapSet.PerformWrite(s => { - var beatmap = beatmapSet.Beatmaps[i]; + for (int i = 0; i < rulesets.Length; i++) + { + var beatmap = s.Beatmaps[i]; - beatmap.StarRating = i + 1; - beatmap.DifficultyName = $"SR{i + 1}"; - } + beatmap.StarRating = i + 1; + beatmap.DifficultyName = $"SR{i + 1}"; + } + }); - return Game.BeatmapManager.Import(beatmapSet)?.Value; + return importedBeatmapSet.Value; } private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null); From 0cfaef36058a374feb31d73a400e3bfc201777dc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 1 Jul 2022 11:21:52 +0300 Subject: [PATCH 267/363] Remove iOS build CI workaround --- .github/workflows/ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c728d89ed1..ef729a779f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,12 +126,6 @@ jobs: with: dotnet-version: "6.0.x" - # macOS agents recently have empty NuGet config files, resulting in restore failures, - # see https://github.com/actions/virtual-environments/issues/5768 - # Add the global nuget package source manually for now. - - name: Setup NuGet.Config - run: echo '' > ~/.config/NuGet/NuGet.Config - # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono # cannot accept .sln(f) files as arguments. # Build just the main game for now. From e213c1a4ef6a489ad1154a353ab0beb4fc310da1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 17:26:40 +0900 Subject: [PATCH 268/363] Rename enum to explicitly mention it is a filter --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 4 ++-- .../OnlinePlay/Lounge/Components/FilterCriteria.cs | 2 +- .../{RoomAccessType.cs => RoomPermissionsFilter.cs} | 2 +- .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 12 ++++++------ .../Multiplayer/MultiplayerLoungeSubScreen.cs | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) rename osu.Game/Screens/OnlinePlay/Lounge/Components/{RoomAccessType.cs => RoomPermissionsFilter.cs} (87%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index d56716a0b5..82e7bf8969 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -170,11 +170,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("both rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2); - AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { AccessType = RoomAccessType.Public }); + AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Public }); AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value)); - AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { AccessType = RoomAccessType.Private }); + AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Private }); AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword.Value)); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index 864634209b..3a687ad351 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -13,6 +13,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public RoomStatusFilter Status; public string Category; public RulesetInfo Ruleset; - public RoomAccessType AccessType; + public RoomPermissionsFilter Permissions; } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPermissionsFilter.cs similarity index 87% rename from osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs rename to osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPermissionsFilter.cs index 1141ed2937..faef2a9d57 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomAccessType.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomPermissionsFilter.cs @@ -3,7 +3,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public enum RoomAccessType + public enum RoomPermissionsFilter { All, Public, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 24dd27c27b..6142fc78a8 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -87,27 +87,27 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); } - matchingFilter &= matchesAccessType(r, criteria.AccessType); + matchingFilter &= matchPermissions(r, criteria.Permissions); r.MatchingFilter = matchingFilter; } }); - static bool matchesAccessType(DrawableLoungeRoom room, RoomAccessType accessType) + static bool matchPermissions(DrawableLoungeRoom room, RoomPermissionsFilter accessType) { switch (accessType) { - case RoomAccessType.All: + case RoomPermissionsFilter.All: return true; - case RoomAccessType.Public: + case RoomPermissionsFilter.Public: return !room.Room.HasPassword.Value; - case RoomAccessType.Private: + case RoomPermissionsFilter.Private: return room.Room.HasPassword.Value; default: - throw new ArgumentOutOfRangeException(nameof(accessType), accessType, $"Unsupported {nameof(RoomAccessType)} in filter"); + throw new ArgumentOutOfRangeException(nameof(accessType), accessType, $"Unsupported {nameof(RoomPermissionsFilter)} in filter"); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index b482b1b680..37b977cff7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } - private Dropdown roomAccessTypeDropdown; + private Dropdown roomAccessTypeDropdown; public override void OnResuming(ScreenTransitionEvent e) { @@ -48,7 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override IEnumerable CreateFilterControls() { - roomAccessTypeDropdown = new SlimEnumDropdown + roomAccessTypeDropdown = new SlimEnumDropdown { RelativeSizeAxes = Axes.None, Width = 160, @@ -63,7 +63,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { var criteria = base.CreateFilterCriteria(); criteria.Category = @"realtime"; - criteria.AccessType = roomAccessTypeDropdown.Current.Value; + criteria.Permissions = roomAccessTypeDropdown.Current.Value; return criteria; } From 5880b824d0e91361de9d6cd2fea7b9fc18fe7f69 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 1 Jul 2022 11:30:51 +0300 Subject: [PATCH 269/363] Simplify description logic for overlay tab items --- osu.Game/Overlays/TabControlOverlayHeader.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index 88ee565859..caec4aeed0 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -110,20 +108,7 @@ namespace osu.Game.Overlays public OverlayHeaderTabItem(T value) : base(value) { - if (!(Value is Enum enumValue)) - Text.Text = Value.ToString().ToLowerInvariant(); - else - { - case LocalisableString localisableString: - Text.Text = localisableString.ToLower(); - break; - - // If localisable == non-localisable, then we must have a basic string, so .ToLowerInvariant() is used. - Text.Text = localisableDescription.Equals(nonLocalisableDescription) - ? nonLocalisableDescription.ToLowerInvariant() - : localisableDescription; - } - + Text.Text = value.GetLocalisableDescription().ToLower(); Text.Font = OsuFont.GetFont(size: 14); Text.Margin = new MarginPadding { Vertical = 16.5f }; // 15px padding + 1.5px line-height difference compensation Bar.Margin = new MarginPadding { Bottom = bar_height }; From 1c2ffb3bc4414b6c8ca51d716b2d745fc53e6d5b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 18:03:26 +0900 Subject: [PATCH 270/363] Fix server-side objects being sent to client --- .../Multiplayer/TestMultiplayerClient.cs | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 6b7495762a..ff83997936 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using MessagePack; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; @@ -50,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// private Room? serverSideAPIRoom; - private MultiplayerPlaylistItem? currentItem => Room?.Playlist[currentIndex]; + private MultiplayerPlaylistItem? currentItem => serverSidePlaylist[currentIndex]; private int currentIndex; private long lastPlaylistItemId; @@ -79,7 +81,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addUser(MultiplayerRoomUser user) { - ((IMultiplayerClient)this).UserJoined(user).WaitSafely(); + ((IMultiplayerClient)this).UserJoined(clone(user)).WaitSafely(); // We want the user to be immediately available for testing, so force a scheduler update to run the update-bound continuation. Scheduler.Update(); @@ -93,7 +95,7 @@ namespace osu.Game.Tests.Visual.Multiplayer .Select(team => (teamID: team.ID, userCount: Room.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) .OrderBy(pair => pair.userCount) .First().teamID; - ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, new TeamVersusUserState { TeamID = bestTeam }).WaitSafely(); + ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(new TeamVersusUserState { TeamID = bestTeam })).WaitSafely(); break; } } @@ -102,7 +104,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Debug.Assert(Room != null); - ((IMultiplayerClient)this).UserLeft(new MultiplayerRoomUser(user.Id)); + ((IMultiplayerClient)this).UserLeft(clone(new MultiplayerRoomUser(user.Id))); Schedule(() => { @@ -113,12 +115,12 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeRoomState(MultiplayerRoomState newState) { - ((IMultiplayerClient)this).RoomStateChanged(newState); + ((IMultiplayerClient)this).RoomStateChanged(clone(newState)); } public void ChangeUserState(int userId, MultiplayerUserState newState) { - ((IMultiplayerClient)this).UserStateChanged(userId, newState); + ((IMultiplayerClient)this).UserStateChanged(clone(userId), clone(newState)); updateRoomStateIfRequired(); } @@ -176,7 +178,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserBeatmapAvailability(int userId, BeatmapAvailability newBeatmapAvailability) { - ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(userId, newBeatmapAvailability); + ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(clone(userId), clone(newBeatmapAvailability)); } protected override async Task JoinRoom(long roomId, string? password = null) @@ -205,7 +207,7 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = serverSideAPIRoom.QueueMode.Value, AutoStartDuration = serverSideAPIRoom.AutoStartDuration.Value }, - Playlist = serverSidePlaylist.ToList(), + Playlist = serverSidePlaylist, Users = { localUser }, Host = localUser }; @@ -216,7 +218,7 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomSetupAction?.Invoke(room); RoomSetupAction = null; - return room; + return clone(room); } protected override void OnRoomJoined() @@ -236,13 +238,13 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } - public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(userId); + public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(clone(userId)); public override Task KickUser(int userId) { Debug.Assert(Room != null); - return ((IMultiplayerClient)this).UserKicked(Room.Users.Single(u => u.UserID == userId)); + return ((IMultiplayerClient)this).UserKicked(clone(Room.Users.Single(u => u.UserID == userId))); } public override async Task ChangeSettings(MultiplayerRoomSettings settings) @@ -256,7 +258,7 @@ namespace osu.Game.Tests.Visual.Multiplayer await changeQueueMode(settings.QueueMode).ConfigureAwait(false); - await ((IMultiplayerClient)this).SettingsChanged(settings).ConfigureAwait(false); + await ((IMultiplayerClient)this).SettingsChanged(clone(settings)).ConfigureAwait(false); foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) ChangeUserState(user.UserID, MultiplayerUserState.Idle); @@ -285,7 +287,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserMods(int userId, IEnumerable newMods) { - ((IMultiplayerClient)this).UserModsChanged(userId, newMods.ToList()); + ((IMultiplayerClient)this).UserModsChanged(clone(userId), clone(newMods)); } public override Task ChangeUserMods(IEnumerable newMods) @@ -312,7 +314,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { userState.TeamID = targetTeam.ID; - await ((IMultiplayerClient)this).MatchUserStateChanged(LocalUser.UserID, userState).ConfigureAwait(false); + await ((IMultiplayerClient)this).MatchUserStateChanged(clone(LocalUser.UserID), clone(userState)).ConfigureAwait(false); } break; @@ -382,7 +384,7 @@ namespace osu.Game.Tests.Visual.Multiplayer serverSidePlaylist[serverSidePlaylist.IndexOf(existingItem)] = item; serverSideAPIRoom.Playlist[serverSideAPIRoom.Playlist.IndexOf(serverSideAPIRoom.Playlist.Single(i => i.ID == item.ID))] = new PlaylistItem(item); - await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemChanged(clone(item)).ConfigureAwait(false); } public override Task EditPlaylistItem(MultiplayerPlaylistItem item) => EditUserPlaylistItem(api.LocalUser.Value.OnlineID, item); @@ -409,7 +411,7 @@ namespace osu.Game.Tests.Visual.Multiplayer serverSidePlaylist.Remove(item); serverSideAPIRoom.Playlist.RemoveAll(i => i.ID == item.ID); - await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemRemoved(clone(playlistItemId)).ConfigureAwait(false); await updateCurrentItem(Room).ConfigureAwait(false); updateRoomStateIfRequired(); @@ -427,14 +429,14 @@ namespace osu.Game.Tests.Visual.Multiplayer await ((IMultiplayerClient)this).MatchRoomStateChanged(null).ConfigureAwait(false); foreach (var user in Room.Users) - await ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, null).ConfigureAwait(false); + await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), null).ConfigureAwait(false); break; case MatchType.TeamVersus: - await ((IMultiplayerClient)this).MatchRoomStateChanged(TeamVersusRoomState.CreateDefault()).ConfigureAwait(false); + await ((IMultiplayerClient)this).MatchRoomStateChanged(clone(TeamVersusRoomState.CreateDefault())).ConfigureAwait(false); foreach (var user in Room.Users) - await ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, new TeamVersusUserState()).ConfigureAwait(false); + await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(new TeamVersusUserState())).ConfigureAwait(false); break; } } @@ -463,7 +465,7 @@ namespace osu.Game.Tests.Visual.Multiplayer currentItem.Expired = true; currentItem.PlayedAt = DateTimeOffset.Now; - await ((IMultiplayerClient)this).PlaylistItemChanged(currentItem).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemChanged(clone(currentItem)).ConfigureAwait(false); await updatePlaylistOrder(Room).ConfigureAwait(false); // In host-only mode, a duplicate playlist item will be used for the next round. @@ -496,7 +498,7 @@ namespace osu.Game.Tests.Visual.Multiplayer serverSidePlaylist.Add(item); serverSideAPIRoom.Playlist.Add(new PlaylistItem(item)); - await ((IMultiplayerClient)this).PlaylistItemAdded(item).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemAdded(clone(item)).ConfigureAwait(false); await updatePlaylistOrder(Room).ConfigureAwait(false); } @@ -514,7 +516,7 @@ namespace osu.Game.Tests.Visual.Multiplayer room.Settings.PlaylistItemId = nextItem.ID; if (notify && nextItem.ID != lastItem) - await ((IMultiplayerClient)this).SettingsChanged(room.Settings).ConfigureAwait(false); + await ((IMultiplayerClient)this).SettingsChanged(clone(room.Settings)).ConfigureAwait(false); } private async Task updatePlaylistOrder(MultiplayerRoom room) @@ -564,12 +566,18 @@ namespace osu.Game.Tests.Visual.Multiplayer item.PlaylistOrder = (ushort)i; - await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); + await ((IMultiplayerClient)this).PlaylistItemChanged(clone(item)).ConfigureAwait(false); } // Also ensure that the API room's playlist is correct. foreach (var item in serverSideAPIRoom.Playlist) item.PlaylistOrder = serverSidePlaylist.Single(i => i.ID == item.ID).PlaylistOrder; } + + private T clone(T incoming) + { + byte[]? serialized = MessagePackSerializer.Serialize(typeof(T), incoming, SignalRUnionWorkaroundResolver.OPTIONS); + return MessagePackSerializer.Deserialize(serialized, SignalRUnionWorkaroundResolver.OPTIONS); + } } } From 0be858b5bf7acbc3906f4c9172a16e4920e2791e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 18:04:29 +0900 Subject: [PATCH 271/363] Rename APIRoom -> ClientAPIRoom, remove unused asserts --- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../TestSceneAllPlayersQueueMode.cs | 26 +++++++++---------- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 24 ++++++++--------- .../Multiplayer/TestSceneMultiplayer.cs | 12 ++++----- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 8 +++--- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 4 +-- .../Multiplayer/TestMultiplayerClient.cs | 10 +------ 9 files changed, 41 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 074a92f5b0..53bed75723 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCreatedWithCorrectMode() { - AddAssert("room created with correct mode", () => MultiplayerClient.APIRoom?.QueueMode.Value == Mode); + AddAssert("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == Mode); } protected void RunGameplay() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 32fd7724e0..b08102df8c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -30,19 +30,19 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] public void TestItemAddedToTheEndOfQueue() { addItem(() => OtherBeatmap); - AddAssert("playlist has 2 items", () => MultiplayerClient.APIRoom?.Playlist.Count == 2); + AddAssert("playlist has 2 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); addItem(() => InitialBeatmap); - AddAssert("playlist has 3 items", () => MultiplayerClient.APIRoom?.Playlist.Count == 3); + AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); - AddAssert("first item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("first item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -50,9 +50,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - AddAssert("playlist has only one item", () => MultiplayerClient.APIRoom?.Playlist.Count == 1); - AddAssert("playlist item is expired", () => MultiplayerClient.APIRoom?.Playlist[0].Expired == true); - AddAssert("last item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1); + AddAssert("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddAssert("last item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -63,13 +63,13 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); - AddAssert("first item expired", () => MultiplayerClient.APIRoom?.Playlist[0].Expired == true); - AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[1].ID); + AddAssert("first item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); RunGameplay(); - AddAssert("second item expired", () => MultiplayerClient.APIRoom?.Playlist[1].Expired == true); - AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[2].ID); + AddAssert("second item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == true); + AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[2].ID); } [Test] @@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(queueMode: QueueMode.HostOnly)); - AddAssert("playlist has 3 items", () => MultiplayerClient.APIRoom?.Playlist.Count == 3); - AddAssert("item 2 is not expired", () => MultiplayerClient.APIRoom?.Playlist[1].Expired == false); + AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); + AddAssert("item 2 is not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); AddAssert("current item is the other beatmap", () => MultiplayerClient.Room?.Settings.PlaylistItemId == 2); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index e436f418ab..8f19590731 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => InitialBeatmap); - AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => OtherBeatmap); - AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[0].ID); + AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -48,10 +48,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - AddAssert("playlist contains two items", () => MultiplayerClient.APIRoom?.Playlist.Count == 2); - AddAssert("first playlist item expired", () => MultiplayerClient.APIRoom?.Playlist[0].Expired == true); - AddAssert("second playlist item not expired", () => MultiplayerClient.APIRoom?.Playlist[1].Expired == false); - AddAssert("second playlist item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.APIRoom?.Playlist[1].ID); + AddAssert("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); + AddAssert("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddAssert("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); + AddAssert("second playlist item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); } [Test] @@ -60,12 +60,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); IBeatmapInfo firstBeatmap = null; - AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.APIRoom?.Playlist[0].Beatmap); + AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ClientAPIRoom?.Playlist[0].Beatmap); selectNewItem(() => OtherBeatmap); - AddAssert("first playlist item hasn't changed", () => MultiplayerClient.APIRoom?.Playlist[0].Beatmap == firstBeatmap); - AddAssert("second playlist item changed", () => MultiplayerClient.APIRoom?.Playlist[1].Beatmap != firstBeatmap); + AddAssert("first playlist item hasn't changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Beatmap == firstBeatmap); + AddAssert("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap); } [Test] @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Multiplayer QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("api room updated", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); } [Test] @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { addItem(() => OtherBeatmap); - AddAssert("playlist contains two items", () => MultiplayerClient.APIRoom?.Playlist.Count == 2); + AddAssert("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); } private void selectNewItem(Func beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index d464527976..37cc803daf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -243,8 +243,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("Check participant count correct", () => multiplayerClient.APIRoom?.ParticipantCount.Value == 1); - AddAssert("Check participant list contains user", () => multiplayerClient.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); + AddAssert("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddAssert("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -303,8 +303,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); - AddAssert("Check participant count correct", () => multiplayerClient.APIRoom?.ParticipantCount.Value == 1); - AddAssert("Check participant list contains user", () => multiplayerClient.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); + AddAssert("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddAssert("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -323,7 +323,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("room has password", () => multiplayerClient.APIRoom?.Password.Value == "password"); + AddAssert("room has password", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password"); } [Test] @@ -377,7 +377,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddStep("change password", () => multiplayerClient.ChangeSettings(password: "password2")); - AddUntilStep("local password changed", () => multiplayerClient.APIRoom?.Password.Value == "password2"); + AddUntilStep("local password changed", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password2"); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index bb7587ac56..478c8d3318 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -254,7 +254,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("last playlist item selected", () => { - var lastItem = this.ChildrenOfType().Single(p => p.Item.ID == MultiplayerClient.APIRoom?.Playlist.Last().ID); + var lastItem = this.ChildrenOfType().Single(p => p.Item.ID == MultiplayerClient.ClientAPIRoom?.Playlist.Last().ID); return lastItem.IsSelectedItem; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 042a9297eb..300980a53f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("initialise gameplay", () => { - Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.APIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) + Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ClientAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, }, MultiplayerClient.Room?.Users.ToArray())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index fcd6dd5bd2..8aa7d5aec2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500, 300), - Items = { BindTarget = MultiplayerClient.APIRoom!.Playlist } + Items = { BindTarget = MultiplayerClient.ClientAPIRoom!.Playlist } }; }); @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestDeleteButtonAlwaysVisibleForHost() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); assertDeleteButtonVisibility(1, true); @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); AddStep("join other user", () => MultiplayerClient.AddUser(new APIUser { Id = 1234 })); AddStep("set other user as host", () => MultiplayerClient.TransferHost(1234)); @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestCurrentItemDoesNotHaveDeleteButton() { AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); - AddUntilStep("wait for queue mode change", () => MultiplayerClient.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 2e39449f64..7d93e550d0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -133,14 +133,14 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("match type head to head", () => multiplayerClient.APIRoom?.Type.Value == MatchType.HeadToHead); + AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.HeadToHead); AddStep("change match type", () => multiplayerClient.ChangeSettings(new MultiplayerRoomSettings { MatchType = MatchType.TeamVersus }).WaitSafely()); - AddUntilStep("api room updated to team versus", () => multiplayerClient.APIRoom?.Type.Value == MatchType.TeamVersus); + AddUntilStep("api room updated to team versus", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.TeamVersus); } [Test] diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index ff83997936..860cf7dee9 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// The local client's . This is not always equivalent to the server-side room. /// - public new Room? APIRoom => base.APIRoom; + public Room? ClientAPIRoom => APIRoom; public Action? RoomSetupAction; @@ -126,8 +126,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private void updateRoomStateIfRequired() { - Debug.Assert(APIRoom != null); - Schedule(() => { Debug.Assert(Room != null); @@ -223,7 +221,6 @@ namespace osu.Game.Tests.Visual.Multiplayer protected override void OnRoomJoined() { - Debug.Assert(APIRoom != null); Debug.Assert(Room != null); // emulate the server sending this after the join room. scheduler required to make sure the join room event is fired first (in Join). @@ -250,7 +247,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public override async Task ChangeSettings(MultiplayerRoomSettings settings) { Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); // Server is authoritative for the time being. @@ -344,7 +340,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item) { Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID) @@ -392,7 +387,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public async Task RemoveUserPlaylistItem(int userId, long playlistItemId) { Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); Debug.Assert(serverSideAPIRoom != null); var item = serverSidePlaylist.Find(i => i.ID == playlistItemId); @@ -444,7 +438,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task changeQueueMode(QueueMode newMode) { Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); // When changing to host-only mode, ensure that at least one non-expired playlist item exists by duplicating the current item. @@ -458,7 +451,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public async Task FinishCurrentItem() { Debug.Assert(Room != null); - Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); // Expire the current playlist item. From b64c0d011c877332d11c19170e620bd2f8a849a3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 18:58:22 +0900 Subject: [PATCH 272/363] Isolate client's Room from TestMultiplayerClient --- .../StatefulMultiplayerClientTest.cs | 10 +- .../TestSceneAllPlayersQueueMode.cs | 12 +- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 8 +- .../Multiplayer/TestSceneMultiplayer.cs | 64 ++-- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../TestSceneMultiplayerParticipantsList.cs | 8 +- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 2 +- .../TestSceneMultiplayerQueueList.cs | 2 +- .../TestSceneMultiplayerSpectateButton.cs | 4 +- .../Visual/Multiplayer/TestSceneTeamVersus.cs | 16 +- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- .../Multiplayer/TestMultiplayerClient.cs | 335 ++++++++++-------- 12 files changed, 260 insertions(+), 205 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 9c8b977ad9..0bcb38327e 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer var user = new APIUser { Id = 33 }; AddRepeatStep("add user multiple times", () => MultiplayerClient.AddUser(user), 3); - AddAssert("room has 2 users", () => MultiplayerClient.Room?.Users.Count == 2); + AddUntilStep("room has 2 users", () => MultiplayerClient.ClientRoom?.Users.Count == 2); } [Test] @@ -33,10 +33,10 @@ namespace osu.Game.Tests.NonVisual.Multiplayer var user = new APIUser { Id = 44 }; AddStep("add user", () => MultiplayerClient.AddUser(user)); - AddAssert("room has 2 users", () => MultiplayerClient.Room?.Users.Count == 2); + AddUntilStep("room has 2 users", () => MultiplayerClient.ClientRoom?.Users.Count == 2); AddRepeatStep("remove user multiple times", () => MultiplayerClient.RemoveUser(user), 3); - AddAssert("room has 1 user", () => MultiplayerClient.Room?.Users.Count == 1); + AddUntilStep("room has 1 user", () => MultiplayerClient.ClientRoom?.Users.Count == 1); } [Test] @@ -59,7 +59,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer changeState(6, MultiplayerUserState.WaitingForLoad); checkPlayingUserCount(6); - AddStep("another user left", () => MultiplayerClient.RemoveUser((MultiplayerClient.Room?.Users.Last().User).AsNonNull())); + AddStep("another user left", () => MultiplayerClient.RemoveUser((MultiplayerClient.ServerRoom?.Users.Last().User).AsNonNull())); checkPlayingUserCount(5); AddStep("leave room", () => MultiplayerClient.LeaveRoom()); @@ -103,7 +103,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer { for (int i = 0; i < userCount; ++i) { - int userId = MultiplayerClient.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!"); + int userId = MultiplayerClient.ServerRoom?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!"); MultiplayerClient.ChangeUserState(userId, state); } }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index b08102df8c..a94a60f12d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("first item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer addItem(() => InitialBeatmap); AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); - AddAssert("first item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("first item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1); AddAssert("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); - AddAssert("last item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("last item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -64,12 +64,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); AddAssert("first item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); - AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); + AddUntilStep("next item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); RunGameplay(); AddAssert("second item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == true); - AddAssert("next item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[2].ID); + AddUntilStep("next item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[2].ID); } [Test] @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(queueMode: QueueMode.HostOnly)); AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); AddAssert("item 2 is not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); - AddAssert("current item is the other beatmap", () => MultiplayerClient.Room?.Settings.PlaylistItemId == 2); + AddUntilStep("current item is the other beatmap", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == 2); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index 8f19590731..15a1354c98 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("first item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => InitialBeatmap); - AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => OtherBeatmap); - AddAssert("playlist item still selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); + AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } [Test] @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); AddAssert("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); AddAssert("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); - AddAssert("second playlist item selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); + AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 37cc803daf..193d1d4248 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -116,25 +116,25 @@ namespace osu.Game.Tests.Visual.Multiplayer // all ready AddUntilStep("all players ready", () => { - var nextUnready = multiplayerClient.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); + var nextUnready = multiplayerClient.ClientRoom?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); if (nextUnready != null) multiplayerClient.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready); - return multiplayerClient.Room?.Users.All(u => u.State == MultiplayerUserState.Ready) == true; + return multiplayerClient.ClientRoom?.Users.All(u => u.State == MultiplayerUserState.Ready) == true; }); AddStep("unready all players at once", () => { - Debug.Assert(multiplayerClient.Room != null); + Debug.Assert(multiplayerClient.ServerRoom != null); - foreach (var u in multiplayerClient.Room.Users) multiplayerClient.ChangeUserState(u.UserID, MultiplayerUserState.Idle); + foreach (var u in multiplayerClient.ServerRoom.Users) multiplayerClient.ChangeUserState(u.UserID, MultiplayerUserState.Idle); }); AddStep("ready all players at once", () => { - Debug.Assert(multiplayerClient.Room != null); + Debug.Assert(multiplayerClient.ServerRoom != null); - foreach (var u in multiplayerClient.Room.Users) multiplayerClient.ChangeUserState(u.UserID, MultiplayerUserState.Ready); + foreach (var u in multiplayerClient.ServerRoom.Users) multiplayerClient.ChangeUserState(u.UserID, MultiplayerUserState.Ready); }); } @@ -146,7 +146,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void removeLastUser() { - APIUser lastUser = multiplayerClient.Room?.Users.Last().User; + APIUser lastUser = multiplayerClient.ServerRoom?.Users.Last().User; if (lastUser == null || lastUser == multiplayerClient.LocalUser?.User) return; @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void kickLastUser() { - APIUser lastUser = multiplayerClient.Room?.Users.Last().User; + APIUser lastUser = multiplayerClient.ServerRoom?.Users.Last().User; if (lastUser == null || lastUser == multiplayerClient.LocalUser?.User) return; @@ -166,14 +166,14 @@ namespace osu.Game.Tests.Visual.Multiplayer private void markNextPlayerReady() { - var nextUnready = multiplayerClient.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); + var nextUnready = multiplayerClient.ServerRoom?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Idle); if (nextUnready != null) multiplayerClient.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Ready); } private void markNextPlayerIdle() { - var nextUnready = multiplayerClient.Room?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Ready); + var nextUnready = multiplayerClient.ServerRoom?.Users.FirstOrDefault(c => c.State == MultiplayerUserState.Ready); if (nextUnready != null) multiplayerClient.ChangeUserState(nextUnready.UserID, MultiplayerUserState.Idle); } @@ -421,22 +421,22 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.Room?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); - AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.Room?.Playlist.First().BeatmapID); + AddUntilStep("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().BeatmapID); AddStep("Select next beatmap", () => InputManager.Key(Key.Down)); - AddUntilStep("Beatmap doesn't match current item", () => Beatmap.Value.BeatmapInfo.OnlineID != multiplayerClient.Room?.Playlist.First().BeatmapID); + AddUntilStep("Beatmap doesn't match current item", () => Beatmap.Value.BeatmapInfo.OnlineID != multiplayerClient.ClientRoom?.Playlist.First().BeatmapID); AddStep("start match externally", () => multiplayerClient.StartMatch().WaitSafely()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); - AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.Room?.Playlist.First().BeatmapID); + AddUntilStep("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().BeatmapID); } [Test] @@ -459,22 +459,22 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.Room?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); - AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.Room?.Playlist.First().RulesetID); + AddUntilStep("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().RulesetID); AddStep("Switch ruleset", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Ruleset.Value = new CatchRuleset().RulesetInfo); - AddUntilStep("Ruleset doesn't match current item", () => Ruleset.Value.OnlineID != multiplayerClient.Room?.Playlist.First().RulesetID); + AddUntilStep("Ruleset doesn't match current item", () => Ruleset.Value.OnlineID != multiplayerClient.ClientRoom?.Playlist.First().RulesetID); AddStep("start match externally", () => multiplayerClient.StartMatch().WaitSafely()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); - AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.Room?.Playlist.First().RulesetID); + AddUntilStep("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().RulesetID); } [Test] @@ -497,25 +497,25 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.Room?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); - AddAssert("Mods match current item", - () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddUntilStep("Mods match current item", + () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.ClientRoom.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); AddStep("Switch required mods", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Mods.Value = new Mod[] { new OsuModDoubleTime() }); - AddAssert("Mods don't match current item", - () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddUntilStep("Mods don't match current item", + () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.ClientRoom.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); AddStep("start match externally", () => multiplayerClient.StartMatch().WaitSafely()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); - AddAssert("Mods match current item", - () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddUntilStep("Mods match current item", + () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.ClientRoom.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); } [Test] @@ -890,7 +890,7 @@ namespace osu.Game.Tests.Visual.Multiplayer RulesetID = new OsuRuleset().RulesetInfo.OnlineID, })).WaitSafely()); - AddUntilStep("item arrived in playlist", () => multiplayerClient.Room?.Playlist.Count == 2); + AddUntilStep("item arrived in playlist", () => multiplayerClient.ClientRoom?.Playlist.Count == 2); AddStep("exit gameplay as initial user", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); AddUntilStep("queue contains item", () => this.ChildrenOfType().Single().Items.Single().ID == 2); @@ -921,10 +921,10 @@ namespace osu.Game.Tests.Visual.Multiplayer RulesetID = new OsuRuleset().RulesetInfo.OnlineID, })).WaitSafely()); - AddUntilStep("item arrived in playlist", () => multiplayerClient.Room?.Playlist.Count == 2); + AddUntilStep("item arrived in playlist", () => multiplayerClient.ClientRoom?.Playlist.Count == 2); AddStep("delete item as other user", () => multiplayerClient.RemoveUserPlaylistItem(1234, 2).WaitSafely()); - AddUntilStep("item removed from playlist", () => multiplayerClient.Room?.Playlist.Count == 1); + AddUntilStep("item removed from playlist", () => multiplayerClient.ClientRoom?.Playlist.Count == 1); AddStep("exit gameplay as initial user", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); AddUntilStep("queue is empty", () => this.ChildrenOfType().Single().Items.Count == 0); @@ -957,7 +957,7 @@ namespace osu.Game.Tests.Visual.Multiplayer runGameplay(); AddStep("exit gameplay for other user", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Idle)); - AddUntilStep("wait for room to be idle", () => multiplayerClient.Room?.State == MultiplayerRoomState.Open); + AddUntilStep("wait for room to be idle", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.Open); runGameplay(); @@ -969,9 +969,9 @@ namespace osu.Game.Tests.Visual.Multiplayer multiplayerClient.StartMatch().WaitSafely(); }); - AddUntilStep("wait for loading", () => multiplayerClient.Room?.State == MultiplayerRoomState.WaitingForLoad); + AddUntilStep("wait for loading", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.WaitingForLoad); AddStep("set player loaded", () => multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Loaded)); - AddUntilStep("wait for gameplay to start", () => multiplayerClient.Room?.State == MultiplayerRoomState.Playing); + AddUntilStep("wait for gameplay to start", () => multiplayerClient.ClientRoom?.State == MultiplayerRoomState.Playing); AddUntilStep("wait for local user to enter spectator", () => multiplayerComponents.CurrentScreen is MultiSpectatorScreen); } } @@ -996,7 +996,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("click ready button", () => { - user = playingUserId == null ? multiplayerClient.LocalUser : multiplayerClient.Room?.Users.Single(u => u.UserID == playingUserId); + user = playingUserId == null ? multiplayerClient.LocalUser : multiplayerClient.ServerRoom?.Users.Single(u => u.UserID == playingUserId); lastState = user?.State ?? MultiplayerUserState.Idle; InputManager.MoveMouseTo(readyButton); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 478c8d3318..f9eec6b728 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); - AddUntilStep("match started", () => MultiplayerClient.Room?.State == MultiplayerRoomState.WaitingForLoad); + AddUntilStep("match started", () => MultiplayerClient.ClientRoom?.State == MultiplayerRoomState.WaitingForLoad); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 75e3da05d3..7db18d1127 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -53,14 +53,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("one unique panel", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 1); AddStep("add non-resolvable user", () => MultiplayerClient.TestAddUnresolvedUser()); - AddAssert("null user added", () => MultiplayerClient.Room.AsNonNull().Users.Count(u => u.User == null) == 1); + AddUntilStep("null user added", () => MultiplayerClient.ClientRoom.AsNonNull().Users.Count(u => u.User == null) == 1); AddUntilStep("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2); AddStep("kick null user", () => this.ChildrenOfType().Single(p => p.User.User == null) .ChildrenOfType().Single().TriggerClick()); - AddAssert("null user kicked", () => MultiplayerClient.Room.AsNonNull().Users.Count == 1); + AddUntilStep("null user kicked", () => MultiplayerClient.ClientRoom.AsNonNull().Users.Count == 1); } [Test] @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("remove host", () => MultiplayerClient.RemoveUser(API.LocalUser.Value)); - AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.User == secondUser); + AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.UserID == secondUser.Id); } [Test] @@ -217,7 +217,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("kick second user", () => this.ChildrenOfType().Single(d => d.IsPresent).TriggerClick()); - AddAssert("second user kicked", () => MultiplayerClient.Room?.Users.Single().UserID == API.LocalUser.Value.Id); + AddUntilStep("second user kicked", () => MultiplayerClient.ClientRoom?.Users.Single().UserID == API.LocalUser.Value.Id); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 300980a53f..9fee771b97 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ClientAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, - }, MultiplayerClient.Room?.Users.ToArray())); + }, MultiplayerClient.ServerRoom?.Users.ToArray())); }); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 8aa7d5aec2..e709a955b3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Visual.Multiplayer assertDeleteButtonVisibility(1, true); AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely()); - AddUntilStep("wait for next item to be selected", () => MultiplayerClient.Room?.Settings.PlaylistItemId == 2); + AddUntilStep("wait for next item to be selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == 2); AddUntilStep("wait for two items in playlist", () => playlist.ChildrenOfType().Count() == 2); assertDeleteButtonVisibility(0, false); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 486da48449..91c87548c7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -99,10 +99,10 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestToggleWhenIdle(MultiplayerUserState initialState) { ClickButtonWhenEnabled(); - AddUntilStep("user is spectating", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Spectating); + AddUntilStep("user is spectating", () => MultiplayerClient.ClientRoom?.Users[0].State == MultiplayerUserState.Spectating); ClickButtonWhenEnabled(); - AddUntilStep("user is idle", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); + AddUntilStep("user is idle", () => MultiplayerClient.ClientRoom?.Users[0].State == MultiplayerUserState.Idle); } [TestCase(MultiplayerRoomState.Closed)] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 7d93e550d0..d80537a2e5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -76,8 +76,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("room type is team vs", () => multiplayerClient.Room?.Settings.MatchType == MatchType.TeamVersus); - AddAssert("user state arrived", () => multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState); + AddUntilStep("room type is team vs", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.TeamVersus); + AddUntilStep("user state arrived", () => multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState); } [Test] @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("user on team 0", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + AddUntilStep("user on team 0", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); AddStep("add another user", () => multiplayerClient.AddUser(new APIUser { Username = "otheruser", Id = 44 })); AddStep("press own button", () => @@ -104,17 +104,17 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(multiplayerComponents.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); - AddAssert("user on team 1", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1); + AddUntilStep("user on team 1", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1); AddStep("press own button again", () => InputManager.Click(MouseButton.Left)); - AddAssert("user on team 0", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + AddUntilStep("user on team 0", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); AddStep("press other user's button", () => { InputManager.MoveMouseTo(multiplayerComponents.ChildrenOfType().ElementAt(1)); InputManager.Click(MouseButton.Left); }); - AddAssert("user still on team 0", () => (multiplayerClient.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); + AddUntilStep("user still on team 0", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0); } [Test] @@ -158,13 +158,13 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("room type is head to head", () => multiplayerClient.Room?.Settings.MatchType == MatchType.HeadToHead); + AddUntilStep("room type is head to head", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.HeadToHead); AddUntilStep("team displays are not displaying teams", () => multiplayerComponents.ChildrenOfType().All(d => d.DisplayedTeam == null)); AddStep("change to team vs", () => multiplayerClient.ChangeSettings(matchType: MatchType.TeamVersus)); - AddUntilStep("room type is team vs", () => multiplayerClient.Room?.Settings.MatchType == MatchType.TeamVersus); + AddUntilStep("room type is team vs", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.TeamVersus); AddUntilStep("team displays are displaying teams", () => multiplayerComponents.ChildrenOfType().All(d => d.DisplayedTeam != null)); } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 22f474ed42..9832acb140 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -413,7 +413,7 @@ namespace osu.Game.Online.Multiplayer UserJoined?.Invoke(user); RoomUpdated?.Invoke(); - }); + }, false); } Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) => diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 860cf7dee9..3165a4ecff 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -31,7 +31,30 @@ namespace osu.Game.Tests.Visual.Multiplayer /// /// The local client's . This is not always equivalent to the server-side room. /// - public Room? ClientAPIRoom => APIRoom; + public Room? ClientAPIRoom => base.APIRoom; + + /// + /// The local client's . This is not always equivalent to the server-side room. + /// + public MultiplayerRoom? ClientRoom => base.Room; + + /// + /// The server's . This is always up-to-date. + /// + public Room? ServerAPIRoom { get; private set; } + + /// + /// The server's . This is always up-to-date. + /// + public MultiplayerRoom? ServerRoom { get; private set; } + + [Obsolete] + protected new Room APIRoom => throw new InvalidOperationException($"Accessing the client-side API room via {nameof(TestMultiplayerClient)} is unsafe. " + + $"Use {nameof(ClientAPIRoom)} if this was intended."); + + [Obsolete] + public new MultiplayerRoom Room => throw new InvalidOperationException($"Accessing the client-side room via {nameof(TestMultiplayerClient)} is unsafe. " + + $"Use {nameof(ClientRoom)} if this was intended."); public Action? RoomSetupAction; @@ -42,17 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private readonly TestMultiplayerRoomManager roomManager; - /// - /// Guaranteed up-to-date playlist. - /// - private readonly List serverSidePlaylist = new List(); - - /// - /// Guaranteed up-to-date API room. - /// - private Room? serverSideAPIRoom; - - private MultiplayerPlaylistItem? currentItem => serverSidePlaylist[currentIndex]; + private MultiplayerPlaylistItem? currentItem => ServerRoom?.Playlist[currentIndex]; private int currentIndex; private long lastPlaylistItemId; @@ -81,150 +94,160 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addUser(MultiplayerRoomUser user) { + Debug.Assert(ServerRoom != null); + + ServerRoom.Users.Add(user); ((IMultiplayerClient)this).UserJoined(clone(user)).WaitSafely(); - // We want the user to be immediately available for testing, so force a scheduler update to run the update-bound continuation. - Scheduler.Update(); - - switch (Room?.MatchState) + switch (ServerRoom?.MatchState) { case TeamVersusRoomState teamVersus: // simulate the server's automatic assignment of users to teams on join. // the "best" team is the one with the least users on it. int bestTeam = teamVersus.Teams - .Select(team => (teamID: team.ID, userCount: Room.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) + .Select(team => (teamID: team.ID, userCount: ServerRoom.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) .OrderBy(pair => pair.userCount) .First().teamID; - ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(new TeamVersusUserState { TeamID = bestTeam })).WaitSafely(); + + user.MatchState = new TeamVersusUserState { TeamID = bestTeam }; + ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(user.MatchState)).WaitSafely(); break; } } public void RemoveUser(APIUser user) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); + ServerRoom.Users.Remove(ServerRoom.Users.Single(u => u.UserID == user.Id)); ((IMultiplayerClient)this).UserLeft(clone(new MultiplayerRoomUser(user.Id))); - Schedule(() => - { - if (Room.Users.Any()) - TransferHost(Room.Users.First().UserID); - }); + if (ServerRoom.Users.Any()) + TransferHost(ServerRoom.Users.First().UserID); } public void ChangeRoomState(MultiplayerRoomState newState) { - ((IMultiplayerClient)this).RoomStateChanged(clone(newState)); + Debug.Assert(ServerRoom != null); + + ServerRoom.State = clone(newState); + + ((IMultiplayerClient)this).RoomStateChanged(clone(ServerRoom.State)); } public void ChangeUserState(int userId, MultiplayerUserState newState) { - ((IMultiplayerClient)this).UserStateChanged(clone(userId), clone(newState)); + Debug.Assert(ServerRoom != null); + + var user = ServerRoom.Users.Single(u => u.UserID == userId); + user.State = clone(newState); + + ((IMultiplayerClient)this).UserStateChanged(clone(userId), clone(user.State)); + updateRoomStateIfRequired(); } private void updateRoomStateIfRequired() { - Schedule(() => + Debug.Assert(ServerRoom != null); + + switch (ServerRoom.State) { - Debug.Assert(Room != null); + case MultiplayerRoomState.Open: + break; - switch (Room.State) - { - case MultiplayerRoomState.Open: - break; + case MultiplayerRoomState.WaitingForLoad: + if (ServerRoom.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad)) + { + var loadedUsers = ServerRoom.Users.Where(u => u.State == MultiplayerUserState.Loaded).ToArray(); - case MultiplayerRoomState.WaitingForLoad: - if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad)) + if (loadedUsers.Length == 0) { - var loadedUsers = Room.Users.Where(u => u.State == MultiplayerUserState.Loaded).ToArray(); - - if (loadedUsers.Length == 0) - { - // all users have bailed from the load sequence. cancel the game start. - ChangeRoomState(MultiplayerRoomState.Open); - return; - } - - foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded)) - ChangeUserState(u.UserID, MultiplayerUserState.Playing); - - ((IMultiplayerClient)this).GameplayStarted(); - - ChangeRoomState(MultiplayerRoomState.Playing); - } - - break; - - case MultiplayerRoomState.Playing: - if (Room.Users.All(u => u.State != MultiplayerUserState.Playing)) - { - foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay)) - ChangeUserState(u.UserID, MultiplayerUserState.Results); - + // all users have bailed from the load sequence. cancel the game start. ChangeRoomState(MultiplayerRoomState.Open); - ((IMultiplayerClient)this).ResultsReady(); - - FinishCurrentItem().WaitSafely(); + return; } - break; - } - }); + foreach (var u in ServerRoom.Users.Where(u => u.State == MultiplayerUserState.Loaded)) + ChangeUserState(u.UserID, MultiplayerUserState.Playing); + + ((IMultiplayerClient)this).GameplayStarted(); + + ChangeRoomState(MultiplayerRoomState.Playing); + } + + break; + + case MultiplayerRoomState.Playing: + if (ServerRoom.Users.All(u => u.State != MultiplayerUserState.Playing)) + { + foreach (var u in ServerRoom.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay)) + ChangeUserState(u.UserID, MultiplayerUserState.Results); + + ChangeRoomState(MultiplayerRoomState.Open); + ((IMultiplayerClient)this).ResultsReady(); + + FinishCurrentItem().WaitSafely(); + } + + break; + } } public void ChangeUserBeatmapAvailability(int userId, BeatmapAvailability newBeatmapAvailability) { - ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(clone(userId), clone(newBeatmapAvailability)); + Debug.Assert(ServerRoom != null); + + var user = ServerRoom.Users.Single(u => u.UserID == userId); + user.BeatmapAvailability = clone(newBeatmapAvailability); + + ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(clone(userId), clone(user.BeatmapAvailability)); } protected override async Task JoinRoom(long roomId, string? password = null) { - serverSideAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == roomId); + ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == roomId); - if (password != serverSideAPIRoom.Password.Value) + if (password != ServerAPIRoom.Password.Value) throw new InvalidOperationException("Invalid password."); - serverSidePlaylist.Clear(); - serverSidePlaylist.AddRange(serverSideAPIRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item))); - lastPlaylistItemId = serverSidePlaylist.Max(item => item.ID); + lastPlaylistItemId = ServerAPIRoom.Playlist.Max(item => item.ID); var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value }; - var room = new MultiplayerRoom(roomId) + ServerRoom = new MultiplayerRoom(roomId) { Settings = { - Name = serverSideAPIRoom.Name.Value, - MatchType = serverSideAPIRoom.Type.Value, + Name = ServerAPIRoom.Name.Value, + MatchType = ServerAPIRoom.Type.Value, Password = password, - QueueMode = serverSideAPIRoom.QueueMode.Value, - AutoStartDuration = serverSideAPIRoom.AutoStartDuration.Value + QueueMode = ServerAPIRoom.QueueMode.Value, + AutoStartDuration = ServerAPIRoom.AutoStartDuration.Value }, - Playlist = serverSidePlaylist, + Playlist = ServerAPIRoom.Playlist.Select(item => new MultiplayerPlaylistItem(item)).ToList(), Users = { localUser }, Host = localUser }; - await updatePlaylistOrder(room).ConfigureAwait(false); - await updateCurrentItem(room, false).ConfigureAwait(false); + await updatePlaylistOrder(ServerRoom).ConfigureAwait(false); + await updateCurrentItem(ServerRoom, false).ConfigureAwait(false); - RoomSetupAction?.Invoke(room); + RoomSetupAction?.Invoke(ServerRoom); RoomSetupAction = null; - return clone(room); + return clone(ServerRoom); } protected override void OnRoomJoined() { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); // emulate the server sending this after the join room. scheduler required to make sure the join room event is fired first (in Join). - changeMatchType(Room.Settings.MatchType).WaitSafely(); + changeMatchType(ServerRoom.Settings.MatchType).WaitSafely(); RoomJoined = true; } @@ -235,28 +258,42 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } - public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(clone(userId)); + public override Task TransferHost(int userId) + { + Debug.Assert(ServerRoom != null); + + ServerRoom.Host = ServerRoom.Users.Single(u => u.UserID == userId); + + return ((IMultiplayerClient)this).HostChanged(clone(userId)); + } public override Task KickUser(int userId) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); - return ((IMultiplayerClient)this).UserKicked(clone(Room.Users.Single(u => u.UserID == userId))); + var user = ServerRoom.Users.Single(u => u.UserID == userId); + + ServerRoom.Users.Remove(user); + + return ((IMultiplayerClient)this).UserKicked(clone(user)); } public override async Task ChangeSettings(MultiplayerRoomSettings settings) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); + settings = clone(settings); + // Server is authoritative for the time being. - settings.PlaylistItemId = Room.Settings.PlaylistItemId; + settings.PlaylistItemId = ServerRoom.Settings.PlaylistItemId; + ServerRoom.Settings = settings; await changeQueueMode(settings.QueueMode).ConfigureAwait(false); await ((IMultiplayerClient)this).SettingsChanged(clone(settings)).ConfigureAwait(false); - foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) + foreach (var user in ServerRoom.Users.Where(u => u.State == MultiplayerUserState.Ready)) ChangeUserState(user.UserID, MultiplayerUserState.Idle); await changeMatchType(settings.MatchType).ConfigureAwait(false); @@ -283,7 +320,12 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserMods(int userId, IEnumerable newMods) { - ((IMultiplayerClient)this).UserModsChanged(clone(userId), clone(newMods)); + Debug.Assert(ServerRoom != null); + + var user = ServerRoom.Users.Single(u => u.UserID == userId); + user.Mods = clone(newMods).ToArray(); + + ((IMultiplayerClient)this).UserModsChanged(clone(userId), clone(user.Mods)); } public override Task ChangeUserMods(IEnumerable newMods) @@ -294,14 +336,14 @@ namespace osu.Game.Tests.Visual.Multiplayer public override async Task SendMatchRequest(MatchUserRequest request) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(LocalUser != null); switch (request) { case ChangeTeamRequest changeTeam: - TeamVersusRoomState roomState = (TeamVersusRoomState)Room.MatchState!; + TeamVersusRoomState roomState = (TeamVersusRoomState)ServerRoom.MatchState!; TeamVersusUserState userState = (TeamVersusUserState)LocalUser.MatchState!; var targetTeam = roomState.Teams.FirstOrDefault(t => t.ID == changeTeam.TeamID); @@ -319,10 +361,10 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task StartMatch() { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); ChangeRoomState(MultiplayerRoomState.WaitingForLoad); - foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) + foreach (var user in ServerRoom.Users.Where(u => u.State == MultiplayerUserState.Ready)) ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad); return ((IMultiplayerClient)this).LoadRequested(); @@ -339,35 +381,35 @@ namespace osu.Game.Tests.Visual.Multiplayer public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); - if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID) + if (ServerRoom.Settings.QueueMode == QueueMode.HostOnly && ServerRoom.Host?.UserID != LocalUser?.UserID) throw new InvalidOperationException("Local user is not the room host."); item.OwnerID = userId; await addItem(item).ConfigureAwait(false); - await updateCurrentItem(Room).ConfigureAwait(false); + await updateCurrentItem(ServerRoom).ConfigureAwait(false); updateRoomStateIfRequired(); } - public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, item); + public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(item)); public async Task EditUserPlaylistItem(int userId, MultiplayerPlaylistItem item) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); - Debug.Assert(serverSideAPIRoom != null); + Debug.Assert(ServerAPIRoom != null); item.OwnerID = userId; - var existingItem = serverSidePlaylist.SingleOrDefault(i => i.ID == item.ID); + var existingItem = ServerRoom.Playlist.SingleOrDefault(i => i.ID == item.ID); if (existingItem == null) throw new InvalidOperationException("Attempted to change an item that doesn't exist."); - if (existingItem.OwnerID != userId && Room.Host?.UserID != LocalUser?.UserID) + if (existingItem.OwnerID != userId && ServerRoom.Host?.UserID != LocalUser?.UserID) throw new InvalidOperationException("Attempted to change an item which is not owned by the user."); if (existingItem.Expired) @@ -376,20 +418,20 @@ namespace osu.Game.Tests.Visual.Multiplayer // Ensure the playlist order doesn't change. item.PlaylistOrder = existingItem.PlaylistOrder; - serverSidePlaylist[serverSidePlaylist.IndexOf(existingItem)] = item; - serverSideAPIRoom.Playlist[serverSideAPIRoom.Playlist.IndexOf(serverSideAPIRoom.Playlist.Single(i => i.ID == item.ID))] = new PlaylistItem(item); + ServerRoom.Playlist[ServerRoom.Playlist.IndexOf(existingItem)] = item; + ServerAPIRoom.Playlist[ServerAPIRoom.Playlist.IndexOf(ServerAPIRoom.Playlist.Single(i => i.ID == item.ID))] = new PlaylistItem(item); await ((IMultiplayerClient)this).PlaylistItemChanged(clone(item)).ConfigureAwait(false); } - public override Task EditPlaylistItem(MultiplayerPlaylistItem item) => EditUserPlaylistItem(api.LocalUser.Value.OnlineID, item); + public override Task EditPlaylistItem(MultiplayerPlaylistItem item) => EditUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(item)); public async Task RemoveUserPlaylistItem(int userId, long playlistItemId) { - Debug.Assert(Room != null); - Debug.Assert(serverSideAPIRoom != null); + Debug.Assert(ServerRoom != null); + Debug.Assert(ServerAPIRoom != null); - var item = serverSidePlaylist.Find(i => i.ID == playlistItemId); + var item = ServerRoom.Playlist.FirstOrDefault(i => i.ID == playlistItemId); if (item == null) throw new InvalidOperationException("Item does not exist in the room."); @@ -403,11 +445,11 @@ namespace osu.Game.Tests.Visual.Multiplayer if (item.Expired) throw new InvalidOperationException("Attempted to remove an item which has already been played."); - serverSidePlaylist.Remove(item); - serverSideAPIRoom.Playlist.RemoveAll(i => i.ID == item.ID); + ServerRoom.Playlist.Remove(item); + ServerAPIRoom.Playlist.RemoveAll(i => i.ID == item.ID); await ((IMultiplayerClient)this).PlaylistItemRemoved(clone(playlistItemId)).ConfigureAwait(false); - await updateCurrentItem(Room).ConfigureAwait(false); + await updateCurrentItem(ServerRoom).ConfigureAwait(false); updateRoomStateIfRequired(); } @@ -415,42 +457,52 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task changeMatchType(MatchType type) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); switch (type) { case MatchType.HeadToHead: - await ((IMultiplayerClient)this).MatchRoomStateChanged(null).ConfigureAwait(false); + ServerRoom.MatchState = null; + await ((IMultiplayerClient)this).MatchRoomStateChanged(clone(ServerRoom.MatchState)).ConfigureAwait(false); + + foreach (var user in ServerRoom.Users) + { + user.MatchState = null; + await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(user.MatchState)).ConfigureAwait(false); + } - foreach (var user in Room.Users) - await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), null).ConfigureAwait(false); break; case MatchType.TeamVersus: - await ((IMultiplayerClient)this).MatchRoomStateChanged(clone(TeamVersusRoomState.CreateDefault())).ConfigureAwait(false); + ServerRoom.MatchState = TeamVersusRoomState.CreateDefault(); + await ((IMultiplayerClient)this).MatchRoomStateChanged(clone(ServerRoom.MatchState)).ConfigureAwait(false); + + foreach (var user in ServerRoom.Users) + { + user.MatchState = new TeamVersusUserState(); + await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(user.MatchState)).ConfigureAwait(false); + } - foreach (var user in Room.Users) - await ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(new TeamVersusUserState())).ConfigureAwait(false); break; } } private async Task changeQueueMode(QueueMode newMode) { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); // When changing to host-only mode, ensure that at least one non-expired playlist item exists by duplicating the current item. - if (newMode == QueueMode.HostOnly && serverSidePlaylist.All(item => item.Expired)) + if (newMode == QueueMode.HostOnly && ServerRoom.Playlist.All(item => item.Expired)) await duplicateCurrentItem().ConfigureAwait(false); - await updatePlaylistOrder(Room).ConfigureAwait(false); - await updateCurrentItem(Room).ConfigureAwait(false); + await updatePlaylistOrder(ServerRoom).ConfigureAwait(false); + await updateCurrentItem(ServerRoom).ConfigureAwait(false); } public async Task FinishCurrentItem() { - Debug.Assert(Room != null); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); // Expire the current playlist item. @@ -458,13 +510,13 @@ namespace osu.Game.Tests.Visual.Multiplayer currentItem.PlayedAt = DateTimeOffset.Now; await ((IMultiplayerClient)this).PlaylistItemChanged(clone(currentItem)).ConfigureAwait(false); - await updatePlaylistOrder(Room).ConfigureAwait(false); + await updatePlaylistOrder(ServerRoom).ConfigureAwait(false); // In host-only mode, a duplicate playlist item will be used for the next round. - if (Room.Settings.QueueMode == QueueMode.HostOnly && serverSidePlaylist.All(item => item.Expired)) + if (ServerRoom.Settings.QueueMode == QueueMode.HostOnly && ServerRoom.Playlist.All(item => item.Expired)) await duplicateCurrentItem().ConfigureAwait(false); - await updateCurrentItem(Room).ConfigureAwait(false); + await updateCurrentItem(ServerRoom).ConfigureAwait(false); } private async Task duplicateCurrentItem() @@ -483,26 +535,28 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task addItem(MultiplayerPlaylistItem item) { - Debug.Assert(Room != null); - Debug.Assert(serverSideAPIRoom != null); + Debug.Assert(ServerRoom != null); + Debug.Assert(ServerAPIRoom != null); item.ID = ++lastPlaylistItemId; - serverSidePlaylist.Add(item); - serverSideAPIRoom.Playlist.Add(new PlaylistItem(item)); + ServerRoom.Playlist.Add(item); + ServerAPIRoom.Playlist.Add(new PlaylistItem(item)); await ((IMultiplayerClient)this).PlaylistItemAdded(clone(item)).ConfigureAwait(false); - await updatePlaylistOrder(Room).ConfigureAwait(false); + await updatePlaylistOrder(ServerRoom).ConfigureAwait(false); } - private IEnumerable upcomingItems => serverSidePlaylist.Where(i => !i.Expired).OrderBy(i => i.PlaylistOrder); + private IEnumerable upcomingItems => ServerRoom?.Playlist.Where(i => !i.Expired).OrderBy(i => i.PlaylistOrder) ?? Enumerable.Empty(); private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true) { - // Pick the next non-expired playlist item by playlist order, or default to the most-recently-expired item. - MultiplayerPlaylistItem nextItem = upcomingItems.FirstOrDefault() ?? serverSidePlaylist.OrderByDescending(i => i.PlayedAt).First(); + Debug.Assert(ServerRoom != null); - currentIndex = serverSidePlaylist.IndexOf(nextItem); + // Pick the next non-expired playlist item by playlist order, or default to the most-recently-expired item. + MultiplayerPlaylistItem nextItem = upcomingItems.FirstOrDefault() ?? ServerRoom.Playlist.OrderByDescending(i => i.PlayedAt).First(); + + currentIndex = ServerRoom.Playlist.IndexOf(nextItem); long lastItem = room.Settings.PlaylistItemId; room.Settings.PlaylistItemId = nextItem.ID; @@ -513,21 +567,22 @@ namespace osu.Game.Tests.Visual.Multiplayer private async Task updatePlaylistOrder(MultiplayerRoom room) { - Debug.Assert(serverSideAPIRoom != null); + Debug.Assert(ServerRoom != null); + Debug.Assert(ServerAPIRoom != null); List orderedActiveItems; switch (room.Settings.QueueMode) { default: - orderedActiveItems = serverSidePlaylist.Where(item => !item.Expired).OrderBy(item => item.ID).ToList(); + orderedActiveItems = ServerRoom.Playlist.Where(item => !item.Expired).OrderBy(item => item.ID).ToList(); break; case QueueMode.AllPlayersRoundRobin: var itemsByPriority = new List<(MultiplayerPlaylistItem item, int priority)>(); // Assign a priority for items from each user, starting from 0 and increasing in order which the user added the items. - foreach (var group in serverSidePlaylist.Where(item => !item.Expired).OrderBy(item => item.ID).GroupBy(item => item.OwnerID)) + foreach (var group in ServerRoom.Playlist.Where(item => !item.Expired).OrderBy(item => item.ID).GroupBy(item => item.OwnerID)) { int priority = 0; itemsByPriority.AddRange(group.Select(item => (item, priority++))); @@ -562,8 +617,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } // Also ensure that the API room's playlist is correct. - foreach (var item in serverSideAPIRoom.Playlist) - item.PlaylistOrder = serverSidePlaylist.Single(i => i.ID == item.ID).PlaylistOrder; + foreach (var item in ServerAPIRoom.Playlist) + item.PlaylistOrder = ServerRoom.Playlist.Single(i => i.ID == item.ID).PlaylistOrder; } private T clone(T incoming) From 37acec1952f58552d3c0d1b721a12561fc71ef7c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 19:15:31 +0900 Subject: [PATCH 273/363] Isolate client's LocalUser from TestMultiplayerClient --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 3165a4ecff..7c14851176 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -56,6 +56,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public new MultiplayerRoom Room => throw new InvalidOperationException($"Accessing the client-side room via {nameof(TestMultiplayerClient)} is unsafe. " + $"Use {nameof(ClientRoom)} if this was intended."); + public new MultiplayerRoomUser? LocalUser => ServerRoom?.Users.SingleOrDefault(u => u.User?.Id == API.LocalUser.Value.Id); + public Action? RoomSetupAction; public bool RoomJoined { get; private set; } From 084a65578cc532482a2377754b72129e090b7deb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 19:36:37 +0900 Subject: [PATCH 274/363] Always clone messages incoming from client-side --- .../Multiplayer/TestMultiplayerClient.cs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7c14851176..19b887eea5 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -201,13 +201,16 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(ServerRoom != null); var user = ServerRoom.Users.Single(u => u.UserID == userId); - user.BeatmapAvailability = clone(newBeatmapAvailability); + user.BeatmapAvailability = newBeatmapAvailability; ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(clone(userId), clone(user.BeatmapAvailability)); } protected override async Task JoinRoom(long roomId, string? password = null) { + roomId = clone(roomId); + password = clone(password); + ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == roomId); if (password != ServerAPIRoom.Password.Value) @@ -262,6 +265,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task TransferHost(int userId) { + userId = clone(userId); + Debug.Assert(ServerRoom != null); ServerRoom.Host = ServerRoom.Users.Single(u => u.UserID == userId); @@ -271,10 +276,11 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task KickUser(int userId) { + userId = clone(userId); + Debug.Assert(ServerRoom != null); var user = ServerRoom.Users.Single(u => u.UserID == userId); - ServerRoom.Users.Remove(user); return ((IMultiplayerClient)this).UserKicked(clone(user)); @@ -282,11 +288,11 @@ namespace osu.Game.Tests.Visual.Multiplayer public override async Task ChangeSettings(MultiplayerRoomSettings settings) { + settings = clone(settings); + Debug.Assert(ServerRoom != null); Debug.Assert(currentItem != null); - settings = clone(settings); - // Server is authoritative for the time being. settings.PlaylistItemId = ServerRoom.Settings.PlaylistItemId; ServerRoom.Settings = settings; @@ -304,40 +310,44 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task ChangeState(MultiplayerUserState newState) { + newState = clone(newState); + if (newState == MultiplayerUserState.Idle && LocalUser?.State == MultiplayerUserState.WaitingForLoad) return Task.CompletedTask; - ChangeUserState(api.LocalUser.Value.Id, newState); + ChangeUserState(api.LocalUser.Value.Id, clone(newState)); return Task.CompletedTask; } public override Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability) { - ChangeUserBeatmapAvailability(api.LocalUser.Value.Id, newBeatmapAvailability); + ChangeUserBeatmapAvailability(api.LocalUser.Value.Id, clone(newBeatmapAvailability)); return Task.CompletedTask; } public void ChangeUserMods(int userId, IEnumerable newMods) - => ChangeUserMods(userId, newMods.Select(m => new APIMod(m)).ToList()); + => ChangeUserMods(userId, newMods.Select(m => new APIMod(m))); public void ChangeUserMods(int userId, IEnumerable newMods) { Debug.Assert(ServerRoom != null); var user = ServerRoom.Users.Single(u => u.UserID == userId); - user.Mods = clone(newMods).ToArray(); + user.Mods = newMods.ToArray(); ((IMultiplayerClient)this).UserModsChanged(clone(userId), clone(user.Mods)); } public override Task ChangeUserMods(IEnumerable newMods) { - ChangeUserMods(api.LocalUser.Value.Id, newMods); + ChangeUserMods(api.LocalUser.Value.Id, clone(newMods)); return Task.CompletedTask; } public override async Task SendMatchRequest(MatchUserRequest request) { + request = clone(request); + Debug.Assert(ServerRoom != null); Debug.Assert(LocalUser != null); @@ -455,7 +465,7 @@ namespace osu.Game.Tests.Visual.Multiplayer updateRoomStateIfRequired(); } - public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId); + public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(playlistItemId)); private async Task changeMatchType(MatchType type) { From 93c68e4329fca6158d62984a0c714eb157f825f5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 19:46:46 +0900 Subject: [PATCH 275/363] More test hardening around ClientAPIRoom usages --- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../Multiplayer/TestSceneAllPlayersQueueMode.cs | 16 ++++++++-------- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 14 +++++++------- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 10 +++++----- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 2 +- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 53bed75723..2b461cf6f6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCreatedWithCorrectMode() { - AddAssert("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == Mode); + AddUntilStep("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == Mode); } protected void RunGameplay() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index a94a60f12d..5947cabf7f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -37,10 +37,10 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestItemAddedToTheEndOfQueue() { addItem(() => OtherBeatmap); - AddAssert("playlist has 2 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); + AddUntilStep("playlist has 2 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); addItem(() => InitialBeatmap); - AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); + AddUntilStep("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); AddUntilStep("first item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } @@ -50,8 +50,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - AddAssert("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1); - AddAssert("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddUntilStep("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1); + AddUntilStep("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); AddUntilStep("last item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID); } @@ -63,12 +63,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); - AddAssert("first item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddUntilStep("first item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); AddUntilStep("next item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); RunGameplay(); - AddAssert("second item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == true); + AddUntilStep("second item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == true); AddUntilStep("next item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[2].ID); } @@ -82,8 +82,8 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(queueMode: QueueMode.HostOnly)); - AddAssert("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); - AddAssert("item 2 is not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); + AddUntilStep("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3); + AddUntilStep("item 2 is not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); AddUntilStep("current item is the other beatmap", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == 2); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index 15a1354c98..800b523a9d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -48,9 +48,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { RunGameplay(); - AddAssert("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); - AddAssert("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); - AddAssert("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); + AddUntilStep("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); + AddUntilStep("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true); + AddUntilStep("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false); AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID); } @@ -60,12 +60,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); IBeatmapInfo firstBeatmap = null; - AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ClientAPIRoom?.Playlist[0].Beatmap); + AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap); selectNewItem(() => OtherBeatmap); - AddAssert("first playlist item hasn't changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Beatmap == firstBeatmap); - AddAssert("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap); + AddUntilStep("first playlist item hasn't changed", () => MultiplayerClient.ServerAPIRoom?.Playlist[0].Beatmap == firstBeatmap); + AddUntilStep("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap); } [Test] @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { addItem(() => OtherBeatmap); - AddAssert("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); + AddUntilStep("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2); } private void selectNewItem(Func beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 193d1d4248..da48fb7332 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -243,8 +243,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); - AddAssert("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); + AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddUntilStep("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -303,8 +303,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddUntilStep("wait for join", () => multiplayerClient.RoomJoined); - AddAssert("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); - AddAssert("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); + AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1); + AddUntilStep("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -323,7 +323,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("room has password", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password"); + AddUntilStep("room has password", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password"); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index f9eec6b728..aa935cb1d4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -254,7 +254,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("last playlist item selected", () => { - var lastItem = this.ChildrenOfType().Single(p => p.Item.ID == MultiplayerClient.ClientAPIRoom?.Playlist.Last().ID); + var lastItem = this.ChildrenOfType().Single(p => p.Item.ID == MultiplayerClient.ServerAPIRoom?.Playlist.Last().ID); return lastItem.IsSelectedItem; }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 9fee771b97..f6a6b3c667 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("initialise gameplay", () => { - Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ClientAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) + Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ServerAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo) { RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, }, MultiplayerClient.ServerRoom?.Users.ToArray())); From 9d28d5f8ee5472356d6ef5fb2a992965b656d751 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 1 Jul 2022 20:43:12 +0900 Subject: [PATCH 276/363] Update SFX for mod overlay show/hide --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 39 ++++++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 69cb3a49fc..bf5200902c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -28,6 +30,9 @@ namespace osu.Game.Overlays.Mods { public const int BUTTON_WIDTH = 200; + protected override string PopInSampleName => ""; + protected override string PopOutSampleName => @"SongSelect/mod-select-overlay-pop-out"; + [Cached] public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); @@ -101,17 +106,21 @@ namespace osu.Game.Overlays.Mods private ShearedToggleButton? customisationButton; + private Sample? columnAppearSample; + protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) : base(colourScheme) { } [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuColour colours) + private void load(OsuGameBase game, OsuColour colours, AudioManager audio) { Header.Title = ModSelectOverlayStrings.ModSelectTitle; Header.Description = ModSelectOverlayStrings.ModSelectDescription; + columnAppearSample = audio.Samples.Get(@"SongSelect/mod-column-pop-in"); + AddRange(new Drawable[] { new ClickToReturnContainer @@ -453,8 +462,32 @@ namespace osu.Game.Overlays.Mods .MoveToY(0, duration, Easing.OutQuint) .FadeIn(duration, Easing.OutQuint); - if (!allFiltered) - nonFilteredColumnCount += 1; + if (allFiltered) + continue; + + int columnNumber = nonFilteredColumnCount; + Scheduler.AddDelayed(() => + { + var channel = columnAppearSample?.GetChannel(); + if (channel == null) return; + + // don't play sample if column isn't visible, but try to play it at least 5 times (assuming there's at least 5 visible columns in `columnFlow`) + if (columnNumber > 5 && !column.Active.Value) return; + + // use X position of the column on screen as a basis for panning the sample + var bounds = column.Parent.BoundingBox; + float balance = (bounds.TopLeft.X + bounds.Width / 2) / RelativeToAbsoluteFactor.X; + + // dip frequency and ramp volume of sample over the first 5 displayed columns + float progress = Math.Min(1, columnNumber / 5f); + + channel.Frequency.Value = 1.3 - (progress * 0.3) + RNG.NextDouble(0.1); + channel.Volume.Value = Math.Max(progress, 0.2); + channel.Balance.Value = -1 + balance * 2; + channel.Play(); + }, delay); + + nonFilteredColumnCount += 1; } } From 6644f9a72a4fd7d9dbf96d00572eb1e57a7019bf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Jul 2022 21:14:05 +0900 Subject: [PATCH 277/363] Fix completely incorrect test --- .../NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 0bcb38327e..d1c5e2d8b3 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer AddStep("add user", () => MultiplayerClient.AddUser(user)); AddUntilStep("room has 2 users", () => MultiplayerClient.ClientRoom?.Users.Count == 2); - AddRepeatStep("remove user multiple times", () => MultiplayerClient.RemoveUser(user), 3); + AddStep("remove user", () => MultiplayerClient.RemoveUser(user)); AddUntilStep("room has 1 user", () => MultiplayerClient.ClientRoom?.Users.Count == 1); } From 76d4f86ca3616bc00b9fb62b62244496ab8c5661 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Fri, 1 Jul 2022 18:21:03 -0700 Subject: [PATCH 278/363] Make legacy slider ball fade out instantly --- .../Skinning/Legacy/LegacySliderBall.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index 07e60c82d0..f3be52b227 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -7,6 +7,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK.Graphics; @@ -18,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private readonly ISkin skin; + private DrawableSlider slider; + private Sprite layerNd; private Sprite layerSpec; @@ -30,8 +34,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } [BackgroundDependencyLoader] - private void load() + private void load(DrawableHitObject dho) { + slider = (DrawableSlider)dho; + var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; InternalChildren = new[] @@ -56,6 +62,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Blending = BlendingParameters.Additive, }, }; + + slider.ApplyCustomUpdateState += updateStateTransforms; } protected override void UpdateAfterChildren() @@ -68,5 +76,25 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy layerNd.Rotation = -appliedRotation; layerSpec.Rotation = -appliedRotation; } + + private void updateStateTransforms(DrawableHitObject obj, ArmedState _) + { + if (obj is not DrawableSlider) + return; + + using (BeginAbsoluteSequence(slider.StateUpdateTime)) + this.FadeIn(); + + using (BeginAbsoluteSequence(slider.HitStateUpdateTime)) + this.FadeOut(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (slider != null) + slider.ApplyCustomUpdateState -= updateStateTransforms; + } } } From 623a99d53abda7e654e4e7a660e0364e2ef54026 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 11:58:45 +0900 Subject: [PATCH 279/363] 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 584fe0a3ef..86f4337521 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 ff223f5107..40c22c1b21 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 b8a4aca02e..09ae70a01f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 634b6cdbbeba1bd768d812fa57eb7ad51db994ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 12:16:16 +0900 Subject: [PATCH 280/363] Send beatmap has to server on solo score request Right now, the client does nothing to ensure a beatmap is in a valid state before requesting to submit a score. There is further work to be done client-side so it is more aware of this state (already handled for playlists, but not for the solo gameplay loop), but the solution I have in mind for that is a bit more involved. This is not used server-side yet, but I want to get this sending so we can start using it for some very basic validation. Will resolve the basic portion of #11922 after implemented server-side. --- osu.Game/Online/Solo/CreateSoloScoreRequest.cs | 10 ++++++---- osu.Game/Screens/Play/SoloPlayer.cs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Solo/CreateSoloScoreRequest.cs b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs index e4612b6659..8c92b32915 100644 --- a/osu.Game/Online/Solo/CreateSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Net.Http; using osu.Framework.IO.Network; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; @@ -13,13 +14,13 @@ namespace osu.Game.Online.Solo { public class CreateSoloScoreRequest : APIRequest { - private readonly int beatmapId; + private readonly BeatmapInfo beatmapInfo; private readonly int rulesetId; private readonly string versionHash; - public CreateSoloScoreRequest(int beatmapId, int rulesetId, string versionHash) + public CreateSoloScoreRequest(BeatmapInfo beatmapInfo, int rulesetId, string versionHash) { - this.beatmapId = beatmapId; + this.beatmapInfo = beatmapInfo; this.rulesetId = rulesetId; this.versionHash = versionHash; } @@ -29,10 +30,11 @@ namespace osu.Game.Online.Solo var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; req.AddParameter("version_hash", versionHash); + req.AddParameter("beatmap_hash", beatmapInfo.MD5Hash); req.AddParameter("ruleset_id", rulesetId.ToString(CultureInfo.InvariantCulture)); return req; } - protected override string Target => $@"beatmaps/{beatmapId}/solo/scores"; + protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/solo/scores"; } } diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index b65602e7a8..565f256277 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Play if (!Ruleset.Value.IsLegacyRuleset()) return null; - return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash); + return new CreateSoloScoreRequest(Beatmap.Value.BeatmapInfo, rulesetId, Game.VersionHash); } protected override bool HandleTokenRetrievalFailure(Exception exception) => false; From 4fd47b5fa0fffdc882779ac11a78b7bbc0f492b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 12:35:29 +0900 Subject: [PATCH 281/363] Add more verbose logging to realm blocking process --- osu.Game.Tests/Database/GeneralUsageTests.cs | 8 ++++---- osu.Game.Tests/Database/RealmLiveTests.cs | 2 +- .../Database/RealmSubscriptionRegistrationTests.cs | 12 ++++++------ osu.Game/Database/EFToRealmMigrator.cs | 2 +- osu.Game/Database/RealmAccess.cs | 11 ++++++++--- osu.Game/OsuGameBase.cs | 2 +- .../Sections/DebugSettings/MemorySettings.cs | 4 ++-- 7 files changed, 23 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs index 5b6f7a0a53..fd0b391d0d 100644 --- a/osu.Game.Tests/Database/GeneralUsageTests.cs +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Database { RunTestWithRealm((realm, _) => { - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { } }); @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Database { Task writeTask; - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { writeTask = realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); Thread.Sleep(100); @@ -169,7 +169,7 @@ namespace osu.Game.Tests.Database Assert.Throws(() => { - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { } }); @@ -177,7 +177,7 @@ namespace osu.Game.Tests.Database stopThreadedUsage.Set(); // Ensure we can block a second time after the usage has ended. - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { } }); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index fd1f564f59..a50eb22c67 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Database { migratedStorage.DeleteDirectory(string.Empty); - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { storage.Migrate(migratedStorage); } diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index c74341b5c9..4ee302bbd0 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Database resolvedItems = null; lastChanges = null; - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) Assert.That(resolvedItems, Is.Empty); realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Database testEventsArriving(false); // And make sure even after another context loss we don't get firings. - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) Assert.That(resolvedItems, Is.Null); realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo())); @@ -212,7 +212,7 @@ namespace osu.Game.Tests.Database Assert.That(beatmapSetInfo, Is.Not.Null); - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { // custom disposal action fired when context lost. Assert.That(beatmapSetInfo, Is.Null); @@ -226,7 +226,7 @@ namespace osu.Game.Tests.Database Assert.That(beatmapSetInfo, Is.Null); - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) Assert.That(beatmapSetInfo, Is.Null); realm.Run(r => r.Refresh()); @@ -251,7 +251,7 @@ namespace osu.Game.Tests.Database Assert.That(receivedValue, Is.Not.Null); receivedValue = null; - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) { } @@ -262,7 +262,7 @@ namespace osu.Game.Tests.Database subscription.Dispose(); receivedValue = null; - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("testing")) Assert.That(receivedValue, Is.Null); realm.Run(r => r.Refresh()); diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 896d111989..8f2ff600d8 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -126,7 +126,7 @@ namespace osu.Game.Database string backupSuffix = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"; // required for initial backup. - var realmBlockOperations = realm.BlockAllOperations(); + var realmBlockOperations = realm.BlockAllOperations("EF migration"); Task.Factory.StartNew(() => { diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 8cf9bb4a47..9c7ecd8a03 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -780,7 +780,7 @@ namespace osu.Game.Database public void CreateBackup(string backupFilename, IDisposable? blockAllOperations = null) { - using (blockAllOperations ?? BlockAllOperations()) + using (blockAllOperations ?? BlockAllOperations("creating backup")) { Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database); @@ -811,9 +811,12 @@ namespace osu.Game.Database /// This should be used in places we need to ensure no ongoing reads/writes are occurring with realm. /// ie. to move the realm backing file to a new location. /// + /// The reason for blocking. Used for logging purposes. /// An which should be disposed to end the blocking section. - public IDisposable BlockAllOperations() + public IDisposable BlockAllOperations(string reason) { + Logger.Log($@"Attempting to block all realm operations for {reason}.", LoggingTarget.Database); + if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); @@ -843,7 +846,7 @@ namespace osu.Game.Database updateRealm = null; } - Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); + Logger.Log(@"Lock acquired for blocking operations", LoggingTarget.Database); const int sleep_length = 200; int timeout = 5000; @@ -867,6 +870,8 @@ namespace osu.Game.Database Logger.Log($"Realm compact failed with error {e}", LoggingTarget.Database); } + Logger.Log(@"Realm usage isolated via compact", LoggingTarget.Database); + // In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm, // and must be posted to the synchronization context. // This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose` diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 9a001c92cd..ead3eeb0dc 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -446,7 +446,7 @@ namespace osu.Game Scheduler.Add(() => { - realmBlocker = realm.BlockAllOperations(); + realmBlocker = realm.BlockAllOperations("migration"); readyToRun.Set(); }, false); diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index 77eede0e46..42ac4adb34 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings Action = () => { // Blocking operations implicitly causes a Compact(). - using (realm.BlockAllOperations()) + using (realm.BlockAllOperations("compact")) { } } @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { try { - var token = realm.BlockAllOperations(); + var token = realm.BlockAllOperations("maintenance"); blockAction.Enabled.Value = false; From 1fd9c6480394798d4b0fb814fbed9a3b067db2c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 12:48:33 +0900 Subject: [PATCH 282/363] Change song select `FooterButton` sound --- osu.Game/Screens/Select/FooterButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index c251e94ef2..6c9032f5d8 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Select private readonly Box light; public FooterButton() - : base(HoverSampleSet.Toolbar) + : base(HoverSampleSet.Default) { AutoSizeAxes = Axes.Both; Shear = SHEAR; From 426c53fe65e07b39e3aadb09c478d94f67d2ad57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 12:50:28 +0900 Subject: [PATCH 283/363] Fix potential null being sent to extension method which doesn't support it --- osu.Game/Skinning/DefaultSkin.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 2467b7d039..5267861e3e 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -113,12 +113,12 @@ namespace osu.Game.Skinning accuracy.Position = new Vector2(-accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding, vertical_offset + 5); accuracy.Origin = Anchor.TopRight; accuracy.Anchor = Anchor.TopCentre; - } - if (combo != null) - { - combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5); - combo.Anchor = Anchor.TopCentre; + if (combo != null) + { + combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5); + combo.Anchor = Anchor.TopCentre; + } } var hitError = container.OfType().FirstOrDefault(); From a03abc747b9ff4042d1be6117f7de620884da249 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 12:58:34 +0900 Subject: [PATCH 284/363] Tidy up comments and simplify bounding box centre logic --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 ++--- osu.Game/Screens/Select/FooterButton.cs | 10 ++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index bf5200902c..04c424461e 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -471,12 +471,11 @@ namespace osu.Game.Overlays.Mods var channel = columnAppearSample?.GetChannel(); if (channel == null) return; - // don't play sample if column isn't visible, but try to play it at least 5 times (assuming there's at least 5 visible columns in `columnFlow`) + // Still play sound effects for off-screen columns up to a certain point. if (columnNumber > 5 && !column.Active.Value) return; // use X position of the column on screen as a basis for panning the sample - var bounds = column.Parent.BoundingBox; - float balance = (bounds.TopLeft.X + bounds.Width / 2) / RelativeToAbsoluteFactor.X; + float balance = column.Parent.BoundingBox.Centre.X / RelativeToAbsoluteFactor.X; // dip frequency and ramp volume of sample over the first 5 displayed columns float progress = Math.Min(1, columnNumber / 5f); diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 6c9032f5d8..3f8cf2e13a 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -4,19 +4,18 @@ #nullable disable using System; -using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; -using osu.Framework.Input.Bindings; -using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select { @@ -68,7 +67,6 @@ namespace osu.Game.Screens.Select private readonly Box light; public FooterButton() - : base(HoverSampleSet.Default) { AutoSizeAxes = Axes.Both; Shear = SHEAR; From 1a14808c396856cabe0048a7d0c0b89b4752cec2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 13:03:13 +0900 Subject: [PATCH 285/363] Fix `BeatmapInfoWedge` test potentially failing due to quick initial load Was doing a comparison of previous vs new content after explicitly doing a load in a previous step. --- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index f9d18f4236..a144111fd3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -53,13 +53,8 @@ namespace osu.Game.Tests.Visual.SongSelect Margin = new MarginPadding { Top = 20 } }); - AddStep("show", () => - { - infoWedge.Show(); - infoWedge.Beatmap = Beatmap.Value; - }); + AddStep("show", () => infoWedge.Show()); - // select part is redundant, but wait for load isn't selectBeatmap(Beatmap.Value.Beatmap); AddWaitStep("wait for select", 3); From 2352636bf2455b8f8e111df0aadc6c2858a7f427 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 13:11:49 +0900 Subject: [PATCH 286/363] Fix ID clash with announce and PM channels in chat overlay tests Yet Another Issue. https://github.com/ppy/osu/runs/7159306924?check_suite_focus=true Not sure why announce channel was using a user id for its channel ID. Bad copy paste? --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 247ea52648..0b982a5745 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -576,12 +576,13 @@ namespace osu.Game.Tests.Visual.Online private Channel createAnnounceChannel() { - int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1); + const int announce_channel_id = 133337; + return new Channel { - Name = $"Announce {id}", + Name = $"Announce {announce_channel_id}", Type = ChannelType.Announce, - Id = id, + Id = announce_channel_id, }; } From 2e3ff2c7e0a86b1d9e03859fa6f83bc057d9d962 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Fri, 1 Jul 2022 21:12:36 -0700 Subject: [PATCH 287/363] Prefer [Resolved] and LoadComplete --- .../Skinning/Legacy/LegacySliderBall.cs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index f3be52b227..fdf7f18bb9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -1,14 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK.Graphics; @@ -20,10 +18,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private readonly ISkin skin; - private DrawableSlider slider; + [Resolved(canBeNull: true)] + private DrawableHitObject? drawableObject { get; set; } - private Sprite layerNd; - private Sprite layerSpec; + private Sprite layerNd = null!; + private Sprite layerSpec = null!; public LegacySliderBall(Drawable animationContent, ISkin skin) { @@ -36,8 +35,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [BackgroundDependencyLoader] private void load(DrawableHitObject dho) { - slider = (DrawableSlider)dho; - var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; InternalChildren = new[] @@ -62,8 +59,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Blending = BlendingParameters.Additive, }, }; + } - slider.ApplyCustomUpdateState += updateStateTransforms; + protected override void LoadComplete() + { + base.LoadComplete(); + + if (drawableObject != null) + { + drawableObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(drawableObject, drawableObject.State.Value); + } } protected override void UpdateAfterChildren() @@ -79,13 +85,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void updateStateTransforms(DrawableHitObject obj, ArmedState _) { - if (obj is not DrawableSlider) - return; - - using (BeginAbsoluteSequence(slider.StateUpdateTime)) + using (BeginAbsoluteSequence(drawableObject.AsNonNull().StateUpdateTime)) this.FadeIn(); - using (BeginAbsoluteSequence(slider.HitStateUpdateTime)) + using (BeginAbsoluteSequence(drawableObject.AsNonNull().HitStateUpdateTime)) this.FadeOut(); } @@ -93,8 +96,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { base.Dispose(isDisposing); - if (slider != null) - slider.ApplyCustomUpdateState -= updateStateTransforms; + if (drawableObject != null) + drawableObject.ApplyCustomUpdateState -= updateStateTransforms; } } } From 6d6ffd35d01c9ffb8d40943fa50776ef3974a2be Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Fri, 1 Jul 2022 21:14:53 -0700 Subject: [PATCH 288/363] Remove unused parameter --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index fdf7f18bb9..57422e3709 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } [BackgroundDependencyLoader] - private void load(DrawableHitObject dho) + private void load() { var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White; From 471b64bd20619ff1124193cde8bc9621bce4d476 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Fri, 1 Jul 2022 21:19:54 -0700 Subject: [PATCH 289/363] Reinstate early return with comment Whoops, didn't read the reply until after I pushed... --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index 57422e3709..e9fa5cbe43 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK.Graphics; @@ -85,6 +86,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void updateStateTransforms(DrawableHitObject obj, ArmedState _) { + // Gets called by slider ticks, tails, etc., leading to duplicated + // animations which in this case have no visual impact (due to + // instant fade) but may negatively affect performance + if (obj is not DrawableSlider) + return; + using (BeginAbsoluteSequence(drawableObject.AsNonNull().StateUpdateTime)) this.FadeIn(); From f2141715b8c6f22eba91df5f98d1f1eb91017c35 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Fri, 1 Jul 2022 21:22:48 -0700 Subject: [PATCH 290/363] Replace AsNonNull() with Assert() --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index e9fa5cbe43..d0745450da 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs @@ -1,8 +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.Diagnostics; using osu.Framework.Allocation; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -92,10 +92,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (obj is not DrawableSlider) return; - using (BeginAbsoluteSequence(drawableObject.AsNonNull().StateUpdateTime)) + Debug.Assert(drawableObject != null); + + using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) this.FadeIn(); - using (BeginAbsoluteSequence(drawableObject.AsNonNull().HitStateUpdateTime)) + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) this.FadeOut(); } From 9a5431d93f2688a01249729619d9154af84f2f9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 13:36:41 +0900 Subject: [PATCH 291/363] Minor renaming / refactoring to use local parameter rather than DI'd object --- .../Skinning/Legacy/LegacySliderBall.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs index d0745450da..414879f42d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private readonly ISkin skin; [Resolved(canBeNull: true)] - private DrawableHitObject? drawableObject { get; set; } + private DrawableHitObject? parentObject { get; set; } private Sprite layerNd = null!; private Sprite layerSpec = null!; @@ -66,10 +65,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { base.LoadComplete(); - if (drawableObject != null) + if (parentObject != null) { - drawableObject.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(drawableObject, drawableObject.State.Value); + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); } } @@ -84,16 +83,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy layerSpec.Rotation = -appliedRotation; } - private void updateStateTransforms(DrawableHitObject obj, ArmedState _) + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState _) { // Gets called by slider ticks, tails, etc., leading to duplicated // animations which in this case have no visual impact (due to // instant fade) but may negatively affect performance - if (obj is not DrawableSlider) + if (drawableObject is not DrawableSlider) return; - Debug.Assert(drawableObject != null); - using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) this.FadeIn(); @@ -105,8 +102,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { base.Dispose(isDisposing); - if (drawableObject != null) - drawableObject.ApplyCustomUpdateState -= updateStateTransforms; + if (parentObject != null) + parentObject.ApplyCustomUpdateState -= updateStateTransforms; } } } From fa5f7c1a00e084eb4498c99228408f17f6172de4 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 13:03:01 +0800 Subject: [PATCH 292/363] Remove the nullable disable annotation in the Replays namespace. --- osu.Game/Replays/Legacy/LegacyReplayFrame.cs | 2 -- osu.Game/Replays/Legacy/ReplayButtonState.cs | 2 -- osu.Game/Replays/Replay.cs | 2 -- 3 files changed, 6 deletions(-) diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs index d0574d65ab..f6abf259e8 100644 --- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using MessagePack; using Newtonsoft.Json; using osu.Framework.Extensions.EnumExtensions; diff --git a/osu.Game/Replays/Legacy/ReplayButtonState.cs b/osu.Game/Replays/Legacy/ReplayButtonState.cs index 7788918ba9..4b02cf2cd5 100644 --- a/osu.Game/Replays/Legacy/ReplayButtonState.cs +++ b/osu.Game/Replays/Legacy/ReplayButtonState.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; namespace osu.Game.Replays.Legacy diff --git a/osu.Game/Replays/Replay.cs b/osu.Game/Replays/Replay.cs index 4903f8c47f..30e176b5c7 100644 --- a/osu.Game/Replays/Replay.cs +++ b/osu.Game/Replays/Replay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Replays; From ea573f314e9db529e163aef109f5f3b3c163df6f Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 13:08:25 +0800 Subject: [PATCH 293/363] Remove the nullable disable annotation in the Rulesets.Replays namespace in the osu.game project. --- osu.Game/Rulesets/Replays/AutoGenerator.cs | 8 ++------ .../Rulesets/Replays/Types/IConvertibleReplayFrame.cs | 4 +--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index 7fab84eba8..f4b96b3884 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.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. -#nullable disable - using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Replays; using osu.Game.Rulesets.Objects; @@ -34,7 +31,7 @@ namespace osu.Game.Rulesets.Replays /// public abstract Replay Generate(); - protected virtual HitObject GetNextObject(int currentIndex) + protected virtual HitObject? GetNextObject(int currentIndex) { if (currentIndex >= Beatmap.HitObjects.Count - 1) return null; @@ -51,8 +48,7 @@ namespace osu.Game.Rulesets.Replays /// protected readonly List Frames = new List(); - [CanBeNull] - protected TFrame LastFrame => Frames.Count == 0 ? null : Frames[^1]; + protected TFrame? LastFrame => Frames.Count == 0 ? null : Frames[^1]; protected AutoGenerator(IBeatmap beatmap) : base(beatmap) diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index 6753a23bca..9a4af9e4ee 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; @@ -19,7 +17,7 @@ namespace osu.Game.Rulesets.Replays.Types /// The to extract values from. /// The beatmap. /// The last post-conversion , used to fill in missing delta information. May be null. - void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null); + void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null); /// /// Populates this using values from a . From c6d0f0f81b11389c817306e534bbcc9b1629bc02 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 13:20:46 +0800 Subject: [PATCH 294/363] pretend that the beatmap property will not be null. Not really throw exception will be the better way? --- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 08e1e78d86..46e58217c5 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -188,7 +188,7 @@ namespace osu.Game.Online.Spectator } if (frame is IConvertibleReplayFrame convertible) - pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); + pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap!)); if (pendingFrames.Count > max_pending_frames) purgePendingFrames(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 85cf463a13..4b1931e7d1 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -143,7 +143,7 @@ namespace osu.Game.Scoring.Legacy return legacyFrame; case IConvertibleReplayFrame convertibleFrame: - return convertibleFrame.ToLegacy(beatmap); + return convertibleFrame.ToLegacy(beatmap!); default: throw new ArgumentException(@"Frame could not be converted to legacy frames", nameof(replayFrame)); From 19721a9bbbff59ca99f19989404885ed0e50d322 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 13:33:05 +0800 Subject: [PATCH 295/363] Remove the nullable disable annotation and fix the breaking api. --- osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs | 2 -- .../Replays/CatchFramedReplayInputHandler.cs | 2 -- osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs | 6 ++---- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 6 ++---- .../Replays/ManiaFramedReplayInputHandler.cs | 2 -- osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs | 4 +--- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 6 ++---- osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs | 2 -- .../Replays/OsuFramedReplayInputHandler.cs | 2 -- osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs | 4 +--- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 2 -- .../Replays/TaikoFramedReplayInputHandler.cs | 2 -- osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs | 4 +--- 13 files changed, 9 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 86c249a7c1..7c84cb24f3 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Utils; diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 64a573149f..b6af88a771 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index 5cf03e4706..e30e535e9b 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; @@ -22,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Replays { } - public CatchReplayFrame(double time, float? position = null, bool dashing = false, CatchReplayFrame lastFrame = null) + public CatchReplayFrame(double time, float? position = null, bool dashing = false, CatchReplayFrame? lastFrame = null) : base(time) { Position = position ?? -1; @@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Replays } } - public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null) { Position = currentFrame.Position.X; Dashing = currentFrame.ButtonState == ReplayButtonState.Left1; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 26572de412..ba4dfc7ab0 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Mania.Beatmaps; @@ -85,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Replays } } - private double calculateReleaseTime(HitObject currentObject, HitObject nextObject) + private double calculateReleaseTime(HitObject currentObject, HitObject? nextObject) { double endTime = currentObject.GetEndTime(); @@ -99,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.Replays return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.StartTime - endTime) * 0.9); } - protected override HitObject GetNextObject(int currentIndex) + protected override HitObject? GetNextObject(int currentIndex) { int desiredColumn = Beatmap.HitObjects[currentIndex].Column; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index c786e1db61..aa164f95da 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 66edc87992..29249ba474 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Game.Beatmaps; @@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Replays Actions.AddRange(actions); } - public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null) { var maniaBeatmap = (ManiaBeatmap)beatmap; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index b0155c02cf..5a3d882ef0 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osuTK; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -95,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Replays { double endTime = prev.GetEndTime(); - HitWindows hitWindows = null; + HitWindows? hitWindows = null; switch (h) { @@ -245,7 +243,7 @@ namespace osu.Game.Rulesets.Osu.Replays } double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime); - OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; + OsuReplayFrame? lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; if (timeDifference > 0) { diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index b41d123380..1cb3208c30 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osuTK; using osu.Game.Beatmaps; using System; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index 8857bfa32d..ea36ecc399 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index 019d8035ed..85060261fe 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; @@ -28,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Replays Actions.AddRange(actions); } - public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null) { Position = currentFrame.Position; if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index f7f72df023..5fd281f9fa 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index 0090409443..2f9b6c7f60 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Replays; using System.Collections.Generic; using System.Linq; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index d8f88785db..a0a687dca6 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; @@ -25,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Replays Actions.AddRange(actions); } - public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null) { if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim); if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim); From 59c83a34235c8fd37a6c8a1fca14b0bbe9f4b2a1 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 13:33:51 +0800 Subject: [PATCH 296/363] Mark as non-null because next object in here will always has value. --- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 2 +- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index ba4dfc7ab0..77ca7ab6b8 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Mania.Replays bool canDelayKeyUpFully = nextObject == null || nextObject.StartTime > endTime + RELEASE_DELAY; - return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.StartTime - endTime) * 0.9); + return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject!.StartTime - endTime) * 0.9); } protected override HitObject? GetNextObject(int currentIndex) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 5fd281f9fa..44c68e6af1 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Replays var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY; - double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.StartTime - endTime) * 0.9; + double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject!.StartTime - endTime) * 0.9; Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay)); hitButton = !hitButton; From 19d300e311438668fea18237a99bf9134a86f4a5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 2 Jul 2022 09:17:38 +0300 Subject: [PATCH 297/363] Fix player loader not accepting star difficulty updates after display --- .../Screens/Play/BeatmapMetadataDisplay.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index dca9401872..0e56cafaaa 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -197,21 +197,19 @@ namespace osu.Game.Screens.Play starRatingDisplay.Show(); } else - { starRatingDisplay.Hide(); - starDifficulty.ValueChanged += d => - { - Debug.Assert(d.NewValue != null); + starDifficulty.ValueChanged += d => + { + Debug.Assert(d.NewValue != null); - starRatingDisplay.Current.Value = d.NewValue.Value; + starRatingDisplay.Current.Value = d.NewValue.Value; - versionFlow.AutoSizeDuration = 300; - versionFlow.AutoSizeEasing = Easing.OutQuint; + versionFlow.AutoSizeDuration = 300; + versionFlow.AutoSizeEasing = Easing.OutQuint; - starRatingDisplay.FadeIn(300, Easing.InQuint); - }; - } + starRatingDisplay.FadeIn(300, Easing.InQuint); + }; } private class MetadataLineLabel : OsuSpriteText From 93809a92d4d9fdf2e355fc81e03c65c783c2958b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 16:30:04 +0900 Subject: [PATCH 298/363] Fix clashing error messaging during realm block operations --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 9c7ecd8a03..cfb0280d2d 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -860,7 +860,7 @@ namespace osu.Game.Database timeout -= sleep_length; if (timeout < 0) - throw new TimeoutException(@"Took too long to acquire lock"); + throw new TimeoutException($@"Realm compact failed after {timeout / sleep_length} attempts over {timeout / 1000} seconds"); } } catch (RealmException e) From e28ee8bc7a7bc6ce7628cc1ee9e50a1c8e783719 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 2 Jul 2022 10:46:52 +0300 Subject: [PATCH 299/363] Count time spent upwards to display attempts correctly --- osu.Game/Database/RealmAccess.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index cfb0280d2d..ed56049064 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -849,7 +849,7 @@ namespace osu.Game.Database Logger.Log(@"Lock acquired for blocking operations", LoggingTarget.Database); const int sleep_length = 200; - int timeout = 5000; + int timeSpent = 0; try { @@ -857,10 +857,10 @@ namespace osu.Game.Database while (!Compact()) { Thread.Sleep(sleep_length); - timeout -= sleep_length; + timeSpent += sleep_length; - if (timeout < 0) - throw new TimeoutException($@"Realm compact failed after {timeout / sleep_length} attempts over {timeout / 1000} seconds"); + if (timeSpent > 5000) + throw new TimeoutException($@"Realm compact failed after {timeSpent / sleep_length} attempts over {timeSpent / 1000} seconds"); } } catch (RealmException e) From 24cd845d1b9f69b24a23f4bcc53cb4d4b4c83933 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jul 2022 17:18:50 +0900 Subject: [PATCH 300/363] 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 86f4337521..ceea60a1c1 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 40c22c1b21..560f7409ff 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 09ae70a01f..0133c6334c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From b626135940eb728c64acc49dd0facc98f7e8b10b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jun 2022 04:39:59 +0900 Subject: [PATCH 301/363] Fix dragging slider bars on nub not performing correctly relative movement --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 3356153e17..7cd89e5b87 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -165,6 +165,9 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(e); } + protected override bool ShouldHandleAsRelativeDrag(MouseDownEvent e) + => Nub.ReceivePositionalInputAt(e.ScreenSpaceMouseDownPosition); + protected override void OnDragEnd(DragEndEvent e) { updateGlow(); From 0a1543c6e8b143c7f3db436c0a0517c93331668d Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sat, 2 Jul 2022 19:48:32 +0800 Subject: [PATCH 302/363] Use `AsNonNull()` instead. --- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 3 ++- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 3 ++- osu.Game/Online/Spectator/SpectatorClient.cs | 3 ++- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 77ca7ab6b8..7c8afdff12 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; @@ -94,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.Replays bool canDelayKeyUpFully = nextObject == null || nextObject.StartTime > endTime + RELEASE_DELAY; - return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject!.StartTime - endTime) * 0.9); + return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.AsNonNull().StartTime - endTime) * 0.9); } protected override HitObject? GetNextObject(int currentIndex) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 44c68e6af1..11136ad695 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Replays; @@ -117,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.Replays var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY; - double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject!.StartTime - endTime) * 0.9; + double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.AsNonNull().StartTime - endTime) * 0.9; Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay)); hitButton = !hitButton; diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 46e58217c5..28aa81c9ee 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -188,7 +189,7 @@ namespace osu.Game.Online.Spectator } if (frame is IConvertibleReplayFrame convertible) - pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap!)); + pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap.AsNonNull())); if (pendingFrames.Count > max_pending_frames) purgePendingFrames(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 4b1931e7d1..44ffe6b6ad 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Text; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; @@ -143,7 +144,7 @@ namespace osu.Game.Scoring.Legacy return legacyFrame; case IConvertibleReplayFrame convertibleFrame: - return convertibleFrame.ToLegacy(beatmap!); + return convertibleFrame.ToLegacy(beatmap.AsNonNull()); default: throw new ArgumentException(@"Frame could not be converted to legacy frames", nameof(replayFrame)); From 7d8ea5e286fca76aba8b7d8202cee750d8bb1404 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Jul 2022 02:59:10 +0900 Subject: [PATCH 303/363] Remove unnecessary proxying --- .../Select/DifficultyRangeFilterControl.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index 7115c59ebf..ac30cc5491 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -23,37 +23,32 @@ namespace osu.Game.Screens.Select private Bindable lowerStars; private Bindable upperStars; - private StarsSlider lowerSlider; - private MaximumStarsSlider upperSlider; - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { const float vertical_offset = 13; - InternalChildren = new[] + InternalChildren = new Drawable[] { new OsuSpriteText { Text = "Difficulty range", Font = OsuFont.GetFont(size: 14), }, - upperSlider = new MaximumStarsSlider + new MaximumStarsSlider { Current = config.GetBindable(OsuSetting.DisplayStarsMaximum), KeyboardStep = 0.1f, RelativeSizeAxes = Axes.X, Y = vertical_offset, }, - lowerSlider = new MinimumStarsSlider + new MinimumStarsSlider { Current = config.GetBindable(OsuSetting.DisplayStarsMinimum), KeyboardStep = 0.1f, RelativeSizeAxes = Axes.X, Y = vertical_offset, - }, - upperSlider.Nub.CreateProxy(), - lowerSlider.Nub.CreateProxy(), + } }; lowerStars = config.GetBindable(OsuSetting.DisplayStarsMinimum); @@ -107,8 +102,6 @@ namespace osu.Game.Screens.Select ? UserInterfaceStrings.NoLimit : Current.Value.ToString(@"0.## stars"); - public new Nub Nub => base.Nub; - protected override void LoadComplete() { base.LoadComplete(); From 052a786eff3233fc8fb04750099705da9033007e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Jul 2022 03:06:03 +0900 Subject: [PATCH 304/363] Only show hover effect on one nub at a time --- osu.Game/Screens/Select/DifficultyRangeFilterControl.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index ac30cc5491..2884211df3 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics; @@ -102,6 +103,12 @@ namespace osu.Game.Screens.Select ? UserInterfaceStrings.NoLimit : Current.Value.ToString(@"0.## stars"); + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + return true; // Make sure only one nub shows hover effect at once. + } + protected override void LoadComplete() { base.LoadComplete(); From 8c2f4b48fc4420e5d0992d0e1a4383dedc711bbd Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 19:27:56 +0800 Subject: [PATCH 305/363] Use debug.assert for better readable. --- osu.Game/Online/Spectator/SpectatorClient.cs | 6 ++++-- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 28aa81c9ee..68c8b57019 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -189,7 +188,10 @@ namespace osu.Game.Online.Spectator } if (frame is IConvertibleReplayFrame convertible) - pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap.AsNonNull())); + { + Debug.Assert(currentBeatmap != null); + pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); + } if (pendingFrames.Count > max_pending_frames) purgePendingFrames(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 44ffe6b6ad..750bb50be3 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using osu.Framework.Extensions; -using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; @@ -144,7 +144,8 @@ namespace osu.Game.Scoring.Legacy return legacyFrame; case IConvertibleReplayFrame convertibleFrame: - return convertibleFrame.ToLegacy(beatmap.AsNonNull()); + Debug.Assert(beatmap != null); + return convertibleFrame.ToLegacy(beatmap); default: throw new ArgumentException(@"Frame could not be converted to legacy frames", nameof(replayFrame)); From 724fd19038851078034c5253d64b669fa2d10eee Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 3 Jul 2022 21:15:13 +0900 Subject: [PATCH 306/363] Remove nullable disables --- .../SongSelect/TestSceneDifficultyRangeFilterControl.cs | 1 - osu.Game/Screens/Select/DifficultyRangeFilterControl.cs | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs index ffad15287b..7ae2c6e5e2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneDifficultyRangeFilterControl.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. -#nullable disable using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Select; diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index 2884211df3..a82c969805 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.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. -#nullable disable using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -21,8 +20,8 @@ namespace osu.Game.Screens.Select { internal class DifficultyRangeFilterControl : CompositeDrawable { - private Bindable lowerStars; - private Bindable upperStars; + private Bindable lowerStars = null!; + private Bindable upperStars = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -97,8 +96,6 @@ namespace osu.Game.Screens.Select private class StarsSlider : OsuSliderBar { - private OsuSpriteText currentDisplay; - public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : Current.Value.ToString(@"0.## stars"); @@ -115,6 +112,8 @@ namespace osu.Game.Screens.Select Nub.Width = Nub.HEIGHT; RangePadding = Nub.Width / 2; + OsuSpriteText currentDisplay; + Nub.Add(currentDisplay = new OsuSpriteText { Anchor = Anchor.Centre, From 59e3d4b9e571cf46abd21e520ba316d4c3bff056 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 21:00:54 +0800 Subject: [PATCH 307/363] Remove the nullable disable annotation in the Ruleset.Filter namespace. --- osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs index fa44f81df3..dd2ad2cbfa 100644 --- a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs +++ b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; From a7bcc32cc21151ff4817e260b73c8f1d1a75b185 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 21:04:39 +0800 Subject: [PATCH 308/363] Remove the nullable disable annotation in the mania ruleset. --- osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs index 58aef4dbf8..c8832dfdfb 100644 --- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mania.Beatmaps; From 14ef7712903145d55ec0c6f0ca01f1bc980c2fd2 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 21:05:14 +0800 Subject: [PATCH 309/363] Remove the nullable disable annotation in the test case. --- osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs | 2 -- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 2657468b03..33204d33a7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets; diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 500f3159e2..bd0617515b 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using NUnit.Framework; using osu.Game.Beatmaps; @@ -256,7 +254,7 @@ namespace osu.Game.Tests.NonVisual.Filtering private class CustomFilterCriteria : IRulesetFilterCriteria { - public string CustomValue { get; set; } + public string? CustomValue { get; set; } public bool Matches(BeatmapInfo beatmapInfo) => true; From e4bf2224c6d1493d54154b72ece66215be5892b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Jul 2022 23:16:51 +0900 Subject: [PATCH 310/363] 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 ceea60a1c1..550088cbfa 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 560f7409ff..d428139343 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 0133c6334c..e3e8aecbbe 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From df152421a8e5b75ab2af2d522343828bce780328 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 3 Jul 2022 10:23:17 -0700 Subject: [PATCH 311/363] Fix personal best score showing delete option on context menu --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index e32bc63aa1..62827f50aa 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -426,10 +426,10 @@ namespace osu.Game.Online.Leaderboards 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, () => new LegacyScoreExporter(storage).Export(Score))); - - if (!isOnlineScope) items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); + } return items.ToArray(); } From 925d7b1fdc10018436071cdae8b0fc654440d0d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 15:01:01 +0900 Subject: [PATCH 312/363] 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 550088cbfa..398c1d91dd 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 d428139343..ed6577651a 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 e3e8aecbbe..7d70b96f62 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 64edc6888d84fc5a6b7e179a8a968c685ef5b19e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 16:45:21 +0900 Subject: [PATCH 313/363] Add test coverage of corrupt realm creating backup --- .../Collections/IO/ImportCollectionsTest.cs | 2 +- .../NonVisual/CustomDataDirectoryTest.cs | 22 +++++++++++++++- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 26 ++++++++++++------- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index c31aafa67f..9a8f29647d 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Collections.IO { string firstRunName; - using (var host = new CleanRunHeadlessGameHost(bypassCleanup: true)) + using (var host = new CleanRunHeadlessGameHost(bypassCleanupOnDispose: true)) { firstRunName = host.Name; diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 216bd0fd3c..216db2121c 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -315,6 +315,26 @@ namespace osu.Game.Tests.NonVisual } } + [Test] + public void TestBackupCreatedOnCorruptRealm() + { + using (var host = new CustomTestHeadlessGameHost()) + { + try + { + File.WriteAllText(host.InitialStorage.GetFullPath(OsuGameBase.CLIENT_DATABASE_FILENAME, true), "i am definitely not a realm file"); + + LoadOsuIntoHost(host); + + Assert.That(host.InitialStorage.GetFiles(string.Empty, "*_corrupt.realm"), Has.One.Items); + } + finally + { + host.Exit(); + } + } + } + private static string getDefaultLocationFor(CustomTestHeadlessGameHost host) { string path = Path.Combine(TestRunHeadlessGameHost.TemporaryTestDirectory, host.Name); @@ -347,7 +367,7 @@ namespace osu.Game.Tests.NonVisual public Storage InitialStorage { get; } public CustomTestHeadlessGameHost([CallerMemberName] string callingMethodName = @"") - : base(callingMethodName: callingMethodName) + : base(callingMethodName: callingMethodName, bypassCleanupOnSetup: true) { string defaultStorageLocation = getDefaultLocationFor(this); diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index d36168d3dd..02d67de5a5 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -15,30 +15,38 @@ namespace osu.Game.Tests /// public class CleanRunHeadlessGameHost : TestRunHeadlessGameHost { + private readonly bool bypassCleanupOnSetup; + /// /// Create a new instance. /// /// Whether to bind IPC channels. /// Whether the host should be forced to run in realtime, rather than accelerated test time. - /// Whether to bypass directory cleanup on host disposal. Should be used only if a subsequent test relies on the files still existing. + /// Whether to bypass directory cleanup on . + /// Whether to bypass directory cleanup on host disposal. Should be used only if a subsequent test relies on the files still existing. /// The name of the calling method, used for test file isolation and clean-up. - public CleanRunHeadlessGameHost(bool bindIPC = false, bool realtime = true, bool bypassCleanup = false, [CallerMemberName] string callingMethodName = @"") + public CleanRunHeadlessGameHost(bool bindIPC = false, bool realtime = true, bool bypassCleanupOnSetup = false, bool bypassCleanupOnDispose = false, + [CallerMemberName] string callingMethodName = @"") : base($"{callingMethodName}-{Guid.NewGuid()}", new HostOptions { BindIPC = bindIPC, - }, bypassCleanup: bypassCleanup, realtime: realtime) + }, bypassCleanup: bypassCleanupOnDispose, realtime: realtime) { + this.bypassCleanupOnSetup = bypassCleanupOnSetup; } protected override void SetupForRun() { - try + if (!bypassCleanupOnSetup) { - Storage.DeleteDirectory(string.Empty); - } - catch - { - // May fail if a logging target has already been set via OsuStorage.ChangeTargetStorage. + try + { + Storage.DeleteDirectory(string.Empty); + } + catch + { + // May fail if a logging target has already been set via OsuStorage.ChangeTargetStorage. + } } // base call needs to be run *after* storage is emptied, as it updates the (static) logger's storage and may start writing From 506409a9c471246dc37fee8ad52fd2634c1d6d19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 16:27:49 +0900 Subject: [PATCH 314/363] Fix realm backup creation failing when run from `RealmAccess` constructor At the point of construction, we are not on the update thread, but it doesn't really matter at this point because there's no other usages. --- osu.Game/Database/EFToRealmMigrator.cs | 5 ++- osu.Game/Database/RealmAccess.cs | 53 +++++++++++++++----------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 8f2ff600d8..3b5424b3fb 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -132,11 +132,12 @@ namespace osu.Game.Database { try { - realm.CreateBackup(Path.Combine(backup_folder, $"client.{backupSuffix}.realm"), realmBlockOperations); + realm.CreateBackup(Path.Combine(backup_folder, $"client.{backupSuffix}.realm")); } finally { - // Above call will dispose of the blocking token when done. + // Once the backup is created, we need to stop blocking operations so the migration can complete. + realmBlockOperations.Dispose(); // Clean up here so we don't accidentally dispose twice. realmBlockOperations = null; } diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index ed56049064..00218e4fe3 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -184,14 +184,14 @@ namespace osu.Game.Database // If a newer version database already exists, don't backup again. We can presume that the first backup is the one we care about. if (!storage.Exists(newerVersionFilename)) - CreateBackup(newerVersionFilename); + createBackup(newerVersionFilename); storage.Delete(Filename); } else { Logger.Error(e, "Realm startup failed with unrecoverable error; starting with a fresh database. A backup of your database has been made."); - CreateBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_corrupt{realm_extension}"); + createBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_corrupt{realm_extension}"); storage.Delete(Filename); } @@ -236,7 +236,7 @@ namespace osu.Game.Database } // For extra safety, also store the temporarily-used database which we are about to replace. - CreateBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_newer_version_before_recovery{realm_extension}"); + createBackup($"{Filename.Replace(realm_extension, string.Empty)}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}_newer_version_before_recovery{realm_extension}"); storage.Delete(Filename); @@ -778,28 +778,37 @@ namespace osu.Game.Database private string? getRulesetShortNameFromLegacyID(long rulesetId) => efContextFactory?.Get().RulesetInfo.FirstOrDefault(r => r.ID == rulesetId)?.ShortName; - public void CreateBackup(string backupFilename, IDisposable? blockAllOperations = null) + /// + /// Create a full realm backup. + /// + /// The filename for the backup. + public void CreateBackup(string backupFilename) { - using (blockAllOperations ?? BlockAllOperations("creating backup")) + if (realmRetrievalLock.CurrentCount != 0) + throw new InvalidOperationException($"Call {nameof(BlockAllOperations)} before creating a backup."); + + createBackup(backupFilename); + } + + private void createBackup(string backupFilename) + { + Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database); + + int attempts = 10; + + while (attempts-- > 0) { - Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database); - - int attempts = 10; - - while (attempts-- > 0) + try { - try - { - using (var source = storage.GetStream(Filename, mode: FileMode.Open)) - using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) - source.CopyTo(destination); - return; - } - catch (IOException) - { - // file may be locked during use. - Thread.Sleep(500); - } + using (var source = storage.GetStream(Filename, mode: FileMode.Open)) + using (var destination = storage.GetStream(backupFilename, FileAccess.Write, FileMode.CreateNew)) + source.CopyTo(destination); + return; + } + catch (IOException) + { + // file may be locked during use. + Thread.Sleep(500); } } } From f1b55d743cdc4e99a5efce22481d87e155e36945 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 4 Jul 2022 11:04:20 -0700 Subject: [PATCH 315/363] Fix delete local score test having no files --- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 31406af87a..cee917f6cf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Models; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; @@ -100,6 +101,7 @@ namespace osu.Game.Tests.Visual.UserInterface Rank = ScoreRank.XH, User = new APIUser { Username = "TestUser" }, Ruleset = new OsuRuleset().RulesetInfo, + Files = { new RealmNamedFileUsage(new RealmFile { Hash = $"{i}" }, string.Empty) } }; importedScores.Add(scoreManager.Import(score).Value); From bb0f2124480ef35736b3aef4a5562e7058260991 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Tue, 5 Jul 2022 00:38:18 +0200 Subject: [PATCH 316/363] Trigger the initial cookie with midi and joysticks --- osu.Game/Screens/Menu/ButtonSystem.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 74be02728f..fadc80277a 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -205,6 +205,28 @@ namespace osu.Game.Screens.Menu return base.OnKeyDown(e); } + protected override bool OnJoystickPress(JoystickPressEvent e) + { + if (State == ButtonSystemState.Initial) + { + logo?.TriggerClick(); + return true; + } + + return base.OnJoystickPress(e); + } + + protected override bool OnMidiDown(MidiDownEvent e) + { + if (State == ButtonSystemState.Initial) + { + logo?.TriggerClick(); + return true; + } + + return base.OnMidiDown(e); + } + public bool OnPressed(KeyBindingPressEvent e) { if (e.Repeat) From 1b98936328df8261bb5c2b1586d794c41f0edc20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 14:59:44 +0900 Subject: [PATCH 317/363] Rename realm `ThreadLocal` to better convey what it's doing Every time I looked at this code I have to re-learn what it's doing. Changing these variable names should help quite a bit. --- osu.Game/Database/RealmAccess.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 00218e4fe3..7ba599bfed 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -66,7 +66,10 @@ namespace osu.Game.Database /// private readonly SemaphoreSlim realmRetrievalLock = new SemaphoreSlim(1); - private readonly ThreadLocal currentThreadCanCreateRealmInstances = new ThreadLocal(); + /// + /// true when the current thread has already entered the . + /// + private readonly ThreadLocal currentThreadHasRealmRetrievalLock = new ThreadLocal(); /// /// Holds a map of functions registered via and and a coinciding action which when triggered, @@ -584,10 +587,11 @@ namespace osu.Game.Database try { - if (!currentThreadCanCreateRealmInstances.Value) + // Ensure that the thread that currently has the `realmRetrievalLock` can retrieve nested contexts and not deadlock on itself. + if (!currentThreadHasRealmRetrievalLock.Value) { realmRetrievalLock.Wait(); - currentThreadCanCreateRealmInstances.Value = true; + currentThreadHasRealmRetrievalLock.Value = true; tookSemaphoreLock = true; } else @@ -611,7 +615,7 @@ namespace osu.Game.Database if (tookSemaphoreLock) { realmRetrievalLock.Release(); - currentThreadCanCreateRealmInstances.Value = false; + currentThreadHasRealmRetrievalLock.Value = false; } } } From 5adec2c7382245ddd0debadaef706876acaf01a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Jul 2022 00:27:38 +0900 Subject: [PATCH 318/363] Ensure blocking restoration only completes after update callback work is completed --- osu.Game/Database/RealmAccess.cs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 7ba599bfed..7dd03d5ce7 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -923,13 +923,34 @@ namespace osu.Game.Database Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); realmRetrievalLock.Release(); + if (syncContext == null) return; + + ManualResetEventSlim updateRealmReestablished = new ManualResetEventSlim(); + // Post back to the update thread to revive any subscriptions. // In the case we are on the update thread, let's also require this to run synchronously. // This requirement is mostly due to test coverage, but shouldn't cause any harm. if (ThreadSafety.IsUpdateThread) - syncContext?.Send(_ => ensureUpdateRealm(), null); + { + syncContext.Send(_ => + { + ensureUpdateRealm(); + updateRealmReestablished.Set(); + }, null); + } else - syncContext?.Post(_ => ensureUpdateRealm(), null); + { + syncContext.Post(_ => + { + ensureUpdateRealm(); + updateRealmReestablished.Set(); + }, null); + } + + // Wait for the post to complete to ensure a second `Migrate` operation doesn't start in the mean time. + // This is important to ensure `ensureUpdateRealm` is run before another blocking migration operation starts. + if (!updateRealmReestablished.Wait(10000)) + throw new TimeoutException(@"Reestablishing update realm after block took too long"); } } From 1e6def8209ddadbebbf9541bc5fb3aeb6683bd29 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Mon, 4 Jul 2022 22:58:41 -0700 Subject: [PATCH 319/363] Fix spinner accent animation on rewind --- .../Skinning/Default/DefaultSpinnerDisc.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs index ab14f939d4..03db76336c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs @@ -137,6 +137,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default this.ScaleTo(initial_scale); this.RotateTo(0); + updateComplete(false, 0); + using (BeginDelayedSequence(spinner.TimePreempt / 2)) { // constant ambient rotation to give the spinner "spinning" character. @@ -177,9 +179,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } } - // transforms we have from completing the spinner will be rolled back, so reapply immediately. - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) - updateComplete(state == ArmedState.Hit, 0); + if (drawableSpinner.Result?.TimeCompleted is double completionTime) + { + using (BeginAbsoluteSequence(completionTime)) + updateComplete(true, 200); + } } private void updateComplete(bool complete, double duration) From 33db5083013bd2433fd16d646bd5dfa8477fa212 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Jul 2022 16:23:10 +0900 Subject: [PATCH 320/363] Add note regarding why the `realmRetrievalLock` return is done on the async thread --- osu.Game/Database/RealmAccess.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 7dd03d5ce7..8cf57b802b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -920,6 +920,8 @@ namespace osu.Game.Database void restoreOperation() { + // Release of lock needs to happen here rather than on the update thread, as there may be another + // operation already blocking the update thread waiting for the blocking operation to complete. Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); realmRetrievalLock.Release(); From 4c3789ec5d67422459154fc452d2a320706ee793 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Tue, 5 Jul 2022 11:15:37 +0200 Subject: [PATCH 321/363] Split into a function --- osu.Game/Screens/Menu/ButtonSystem.cs | 30 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index fadc80277a..04bffda81b 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -191,38 +191,44 @@ namespace osu.Game.Screens.Menu State = ButtonSystemState.Initial; } - protected override bool OnKeyDown(KeyDownEvent e) + /// + /// Triggers the if the current is . + /// + /// true if the was triggered, false otherwise. + private bool triggerInitialOsuLogo() { - if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed || e.SuperPressed) - return false; - if (State == ButtonSystemState.Initial) { logo?.TriggerClick(); return true; } + return false; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed || e.SuperPressed) + return false; + + if (triggerInitialOsuLogo()) + return true; + return base.OnKeyDown(e); } protected override bool OnJoystickPress(JoystickPressEvent e) { - if (State == ButtonSystemState.Initial) - { - logo?.TriggerClick(); + if (triggerInitialOsuLogo()) return true; - } return base.OnJoystickPress(e); } protected override bool OnMidiDown(MidiDownEvent e) { - if (State == ButtonSystemState.Initial) - { - logo?.TriggerClick(); + if (triggerInitialOsuLogo()) return true; - } return base.OnMidiDown(e); } From d217d668526745e63e8f0c3c08139e2f997a2c9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 17:18:33 +0900 Subject: [PATCH 322/363] Add `OnlineMetadataClient` --- osu.Game/Online/EndpointConfiguration.cs | 5 ++ osu.Game/Online/Metadata/BeatmapUpdates.cs | 28 +++++++++ osu.Game/Online/Metadata/IMetadataClient.cs | 12 ++++ osu.Game/Online/Metadata/IMetadataServer.cs | 21 +++++++ osu.Game/Online/Metadata/MetadataClient.cs | 15 +++++ .../Online/Metadata/OnlineMetadataClient.cs | 61 +++++++++++++++++++ osu.Game/OsuGameBase.cs | 8 ++- 7 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Metadata/BeatmapUpdates.cs create mode 100644 osu.Game/Online/Metadata/IMetadataClient.cs create mode 100644 osu.Game/Online/Metadata/IMetadataServer.cs create mode 100644 osu.Game/Online/Metadata/MetadataClient.cs create mode 100644 osu.Game/Online/Metadata/OnlineMetadataClient.cs diff --git a/osu.Game/Online/EndpointConfiguration.cs b/osu.Game/Online/EndpointConfiguration.cs index af4e88a05c..f3bcced630 100644 --- a/osu.Game/Online/EndpointConfiguration.cs +++ b/osu.Game/Online/EndpointConfiguration.cs @@ -39,5 +39,10 @@ namespace osu.Game.Online /// The endpoint for the SignalR multiplayer server. /// public string MultiplayerEndpointUrl { get; set; } + + /// + /// The endpoint for the SignalR metadata server. + /// + public string MetadataEndpointUrl { get; set; } } } diff --git a/osu.Game/Online/Metadata/BeatmapUpdates.cs b/osu.Game/Online/Metadata/BeatmapUpdates.cs new file mode 100644 index 0000000000..8814e35e1f --- /dev/null +++ b/osu.Game/Online/Metadata/BeatmapUpdates.cs @@ -0,0 +1,28 @@ +// 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 MessagePack; + +namespace osu.Game.Online.Metadata +{ + /// + /// Describes a set of beatmaps which have been updated in some way. + /// + [MessagePackObject] + [Serializable] + public class BeatmapUpdates + { + [Key(0)] + public int[] BeatmapSetIDs { get; set; } + + [Key(1)] + public uint LastProcessedQueueID { get; set; } + + public BeatmapUpdates(int[] beatmapSetIDs, uint lastProcessedQueueID) + { + BeatmapSetIDs = beatmapSetIDs; + LastProcessedQueueID = lastProcessedQueueID; + } + } +} diff --git a/osu.Game/Online/Metadata/IMetadataClient.cs b/osu.Game/Online/Metadata/IMetadataClient.cs new file mode 100644 index 0000000000..ad1e7ebbaf --- /dev/null +++ b/osu.Game/Online/Metadata/IMetadataClient.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; + +namespace osu.Game.Online.Metadata +{ + public interface IMetadataClient + { + Task BeatmapSetsUpdated(BeatmapUpdates updates); + } +} diff --git a/osu.Game/Online/Metadata/IMetadataServer.cs b/osu.Game/Online/Metadata/IMetadataServer.cs new file mode 100644 index 0000000000..edf288b6cf --- /dev/null +++ b/osu.Game/Online/Metadata/IMetadataServer.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; + +namespace osu.Game.Online.Metadata +{ + /// + /// Metadata server is responsible for keeping the osu! client up-to-date with any changes. + /// + public interface IMetadataServer + { + /// + /// Get any changes since a specific point in the queue. + /// Should be used to allow the client to catch up with any changes after being closed or disconnected. + /// + /// The last processed queue ID. + /// + Task GetChangesSince(uint queueId); + } +} diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs new file mode 100644 index 0000000000..65817b3152 --- /dev/null +++ b/osu.Game/Online/Metadata/MetadataClient.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; +using osu.Framework.Graphics; + +namespace osu.Game.Online.Metadata +{ + public abstract class MetadataClient : Component, IMetadataClient, IMetadataServer + { + public abstract Task BeatmapSetsUpdated(BeatmapUpdates updates); + + public abstract Task GetChangesSince(uint queueId); + } +} diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs new file mode 100644 index 0000000000..cfcebf73d3 --- /dev/null +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -0,0 +1,61 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.SignalR.Client; +using osu.Framework.Allocation; +using osu.Framework.Logging; +using osu.Game.Online.API; + +namespace osu.Game.Online.Metadata +{ + public class OnlineMetadataClient : MetadataClient + { + private readonly string endpoint; + + private IHubClientConnector? connector; + + private HubConnection? connection => connector?.CurrentConnection; + + public OnlineMetadataClient(EndpointConfiguration endpoints) + { + endpoint = endpoints.MetadataEndpointUrl; + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + // Importantly, we are intentionally not using MessagePack here to correctly support derived class serialization. + // More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code. + connector = api.GetHubConnector(nameof(OnlineMetadataClient), endpoint); + + if (connector != null) + { + connector.ConfigureConnection = connection => + { + // this is kind of SILLY + // https://github.com/dotnet/aspnetcore/issues/15198 + connection.On(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated); + }; + } + } + + public override Task BeatmapSetsUpdated(BeatmapUpdates updates) + { + Logger.Log($"Received beatmap updates {updates.BeatmapSetIDs.Length} updates with last id {updates.LastProcessedQueueID}"); + return Task.CompletedTask; + } + + public override Task GetChangesSince(uint queueId) + { + throw new NotImplementedException(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + connector?.Dispose(); + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ead3eeb0dc..bf3cc16728 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; using System.Threading; @@ -41,6 +40,7 @@ using osu.Game.IO; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.Chat; +using osu.Game.Online.Metadata; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Overlays; @@ -52,6 +52,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Utils; +using File = System.IO.File; using RuntimeInfo = osu.Framework.RuntimeInfo; namespace osu.Game @@ -180,6 +181,8 @@ namespace osu.Game private MultiplayerClient multiplayerClient; + private MetadataClient metadataClient; + private RealmAccess realm; protected override Container Content => content; @@ -265,6 +268,7 @@ namespace osu.Game dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); + dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); @@ -316,8 +320,10 @@ namespace osu.Game // add api components to hierarchy. if (API is APIAccess apiAccess) AddInternal(apiAccess); + AddInternal(spectatorClient); AddInternal(multiplayerClient); + AddInternal(metadataClient); AddInternal(rulesetConfigCache); From b0d4f7aff6f26724e4a59464db4998e672b78a33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 17:49:08 +0900 Subject: [PATCH 323/363] Add recovery logic after disconnection --- .../Online/Metadata/OnlineMetadataClient.cs | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index cfcebf73d3..0d31060a8b 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -1,10 +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; +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Online.API; @@ -16,6 +17,8 @@ namespace osu.Game.Online.Metadata private IHubClientConnector? connector; + private uint? lastQueueId; + private HubConnection? connection => connector?.CurrentConnection; public OnlineMetadataClient(EndpointConfiguration endpoints) @@ -38,18 +41,78 @@ namespace osu.Game.Online.Metadata // https://github.com/dotnet/aspnetcore/issues/15198 connection.On(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated); }; + + connector.IsConnected.BindValueChanged(isConnectedChanged, true); } } - public override Task BeatmapSetsUpdated(BeatmapUpdates updates) + private bool catchingUp; + + private void isConnectedChanged(ValueChangedEvent connected) + { + if (!connected.NewValue) + return; + + if (lastQueueId != null) + { + catchingUp = true; + + Task.Run(async () => + { + try + { + while (true) + { + Logger.Log($"Requesting catch-up from {lastQueueId.Value}"); + var catchUpChanges = await GetChangesSince(lastQueueId.Value); + + lastQueueId = catchUpChanges.LastProcessedQueueID; + + if (catchUpChanges.BeatmapSetIDs.Length == 0) + { + Logger.Log($"Catch-up complete at {lastQueueId.Value}"); + break; + } + + await ProcessChanges(catchUpChanges.BeatmapSetIDs); + } + } + finally + { + catchingUp = false; + } + }); + } + } + + public override async Task BeatmapSetsUpdated(BeatmapUpdates updates) { Logger.Log($"Received beatmap updates {updates.BeatmapSetIDs.Length} updates with last id {updates.LastProcessedQueueID}"); + + // If we're still catching up, avoid updating the last ID as it will interfere with catch-up efforts. + if (!catchingUp) + lastQueueId = updates.LastProcessedQueueID; + + await ProcessChanges(updates.BeatmapSetIDs); + } + + protected Task ProcessChanges(int[] beatmapSetIDs) + { + foreach (int id in beatmapSetIDs) + Logger.Log($"Processing {id}..."); return Task.CompletedTask; } public override Task GetChangesSince(uint queueId) { - throw new NotImplementedException(); + if (connector?.IsConnected.Value != true) + return Task.FromCanceled(default); + + Logger.Log($"Requesting any changes since last known queue id {queueId}"); + + Debug.Assert(connection != null); + + return connection.InvokeAsync(nameof(IMetadataServer.GetChangesSince), queueId); } protected override void Dispose(bool isDisposing) From 59d0bac728546fbc5f018b8b94d89aae87f0d333 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Jul 2022 18:13:53 +0900 Subject: [PATCH 324/363] Hook up update flow to metadata stream --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 8 +++---- osu.Game/Beatmaps/BeatmapImporter.cs | 7 +++--- osu.Game/Beatmaps/BeatmapManager.cs | 24 ++++++------------- osu.Game/Beatmaps/BeatmapUpdater.cs | 16 ++++++++++++- .../Online/Metadata/OnlineMetadataClient.cs | 9 ++++++- osu.Game/OsuGameBase.cs | 18 ++++++++++---- 6 files changed, 50 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 0bf47141e4..fcf69bf6f2 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -212,17 +212,17 @@ namespace osu.Game.Tests.Online { } - protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapUpdater beatmapUpdater) + protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm) { - return new TestBeatmapImporter(this, storage, realm, beatmapUpdater); + return new TestBeatmapImporter(this, storage, realm); } internal class TestBeatmapImporter : BeatmapImporter { private readonly TestBeatmapManager testBeatmapManager; - public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapUpdater beatmapUpdater) - : base(storage, databaseAccess, beatmapUpdater) + public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess) + : base(storage, databaseAccess) { this.testBeatmapManager = testBeatmapManager; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 5382b98c22..92f1fc17d5 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -31,12 +31,11 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - private readonly BeatmapUpdater? beatmapUpdater; + public Action? ProcessBeatmap { private get; set; } - public BeatmapImporter(Storage storage, RealmAccess realm, BeatmapUpdater? beatmapUpdater = null) + public BeatmapImporter(Storage storage, RealmAccess realm) : base(storage, realm) { - this.beatmapUpdater = beatmapUpdater; } protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; @@ -100,7 +99,7 @@ namespace osu.Game.Beatmaps { base.PostImport(model, realm); - beatmapUpdater?.Process(model); + ProcessBeatmap?.Invoke(model); } private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e16a87eb50..b1acf78ec6 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -34,14 +34,15 @@ namespace osu.Game.Beatmaps /// Handles general operations related to global beatmap management. /// [ExcludeFromDynamicCompile] - public class BeatmapManager : ModelManager, IModelImporter, IWorkingBeatmapCache, IDisposable + public class BeatmapManager : ModelManager, IModelImporter, IWorkingBeatmapCache { public ITrackStore BeatmapTrackStore { get; } private readonly BeatmapImporter beatmapImporter; private readonly WorkingBeatmapCache workingBeatmapCache; - private readonly BeatmapUpdater? beatmapUpdater; + + public Action? ProcessBeatmap { private get; set; } public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false) @@ -54,15 +55,14 @@ namespace osu.Game.Beatmaps if (difficultyCache == null) throw new ArgumentNullException(nameof(difficultyCache), "Difficulty cache must be provided if online lookups are required."); - - beatmapUpdater = new BeatmapUpdater(this, difficultyCache, api, storage); } var userResources = new RealmFileStore(realm, storage).Store; BeatmapTrackStore = audioManager.GetTrackStore(userResources); - beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, beatmapUpdater); + beatmapImporter = CreateBeatmapImporter(storage, realm); + beatmapImporter.ProcessBeatmap = obj => ProcessBeatmap?.Invoke(obj); beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); @@ -74,8 +74,7 @@ namespace osu.Game.Beatmaps return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); } - protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapUpdater? beatmapUpdater) => - new BeatmapImporter(storage, realm, beatmapUpdater); + protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm) => new BeatmapImporter(storage, realm); /// /// Create a new beatmap set, backed by a model, @@ -323,7 +322,7 @@ namespace osu.Game.Beatmaps setInfo.CopyChangesToRealm(liveBeatmapSet); - beatmapUpdater?.Process(liveBeatmapSet, r); + ProcessBeatmap?.Invoke(liveBeatmapSet); }); } @@ -468,15 +467,6 @@ namespace osu.Game.Beatmaps #endregion - #region Implementation of IDisposable - - public void Dispose() - { - beatmapUpdater?.Dispose(); - } - - #endregion - #region Implementation of IPostImports public Action>>? PresentImport diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index d800b09a2b..d0ec44a034 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -31,6 +31,14 @@ namespace osu.Game.Beatmaps onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); } + /// + /// Queue a beatmap for background processing. + /// + public void Queue(int beatmapSetId) + { + // TODO: implement + } + /// /// Queue a beatmap for background processing. /// @@ -44,7 +52,13 @@ namespace osu.Game.Beatmaps /// /// Run all processing on a beatmap immediately. /// - public void Process(BeatmapSetInfo beatmapSet) => beatmapSet.Realm.Write(r => Process(beatmapSet, r)); + public void Process(BeatmapSetInfo beatmapSet) + { + if (beatmapSet.Realm.IsInTransaction) + Process(beatmapSet, beatmapSet.Realm); + else + beatmapSet.Realm.Write(r => Process(beatmapSet, r)); + } public void Process(BeatmapSetInfo beatmapSet, Realm realm) { diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 0d31060a8b..b05efd5311 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -7,12 +7,14 @@ using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Game.Beatmaps; using osu.Game.Online.API; namespace osu.Game.Online.Metadata { public class OnlineMetadataClient : MetadataClient { + private readonly BeatmapUpdater beatmapUpdater; private readonly string endpoint; private IHubClientConnector? connector; @@ -21,8 +23,9 @@ namespace osu.Game.Online.Metadata private HubConnection? connection => connector?.CurrentConnection; - public OnlineMetadataClient(EndpointConfiguration endpoints) + public OnlineMetadataClient(EndpointConfiguration endpoints, BeatmapUpdater beatmapUpdater) { + this.beatmapUpdater = beatmapUpdater; endpoint = endpoints.MetadataEndpointUrl; } @@ -99,7 +102,11 @@ namespace osu.Game.Online.Metadata protected Task ProcessChanges(int[] beatmapSetIDs) { foreach (int id in beatmapSetIDs) + { Logger.Log($"Processing {id}..."); + beatmapUpdater.Queue(id); + } + return Task.CompletedTask; } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index bf3cc16728..356013a410 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -171,6 +171,7 @@ namespace osu.Game public readonly Bindable>> AvailableMods = new Bindable>>(new Dictionary>()); private BeatmapDifficultyCache difficultyCache; + private BeatmapUpdater beatmapUpdater; private UserLookupCache userCache; private BeatmapLookupCache beatmapCache; @@ -266,16 +267,13 @@ namespace osu.Game dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints, VersionHash)); - dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); - dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); - dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); - var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, difficultyCache, LocalConfig)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); @@ -284,6 +282,15 @@ namespace osu.Game // Add after all the above cache operations as it depends on them. AddInternal(difficultyCache); + // TODO: OsuGame or OsuGameBase? + beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage); + + dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); + dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); + dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints, beatmapUpdater)); + + BeatmapManager.ProcessBeatmap = set => beatmapUpdater.Process(set); + dependencies.Cache(userCache = new UserLookupCache()); AddInternal(userCache); @@ -580,9 +587,10 @@ namespace osu.Game base.Dispose(isDisposing); RulesetStore?.Dispose(); - BeatmapManager?.Dispose(); LocalConfig?.Dispose(); + beatmapUpdater?.Dispose(); + realm?.Dispose(); if (Host != null) From bdd1bf4da00d8d8de36370c2054c6d93b7ba562b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Jul 2022 21:42:35 +0900 Subject: [PATCH 325/363] Save last processed id to config for now --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ osu.Game/Online/Metadata/BeatmapUpdates.cs | 4 ++-- osu.Game/Online/Metadata/IMetadataServer.cs | 2 +- osu.Game/Online/Metadata/MetadataClient.cs | 2 +- osu.Game/Online/Metadata/OnlineMetadataClient.cs | 15 +++++++++------ 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 713166a9a0..a523507205 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -167,6 +167,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full); SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f); + + SetDefault(OsuSetting.LastProcessedMetadataId, -1); } public IDictionary GetLoggableState() => @@ -363,5 +365,6 @@ namespace osu.Game.Configuration DiscordRichPresence, AutomaticallyDownloadWhenSpectating, ShowOnlineExplicitContent, + LastProcessedMetadataId } } diff --git a/osu.Game/Online/Metadata/BeatmapUpdates.cs b/osu.Game/Online/Metadata/BeatmapUpdates.cs index 8814e35e1f..a0cf616c70 100644 --- a/osu.Game/Online/Metadata/BeatmapUpdates.cs +++ b/osu.Game/Online/Metadata/BeatmapUpdates.cs @@ -17,9 +17,9 @@ namespace osu.Game.Online.Metadata public int[] BeatmapSetIDs { get; set; } [Key(1)] - public uint LastProcessedQueueID { get; set; } + public int LastProcessedQueueID { get; set; } - public BeatmapUpdates(int[] beatmapSetIDs, uint lastProcessedQueueID) + public BeatmapUpdates(int[] beatmapSetIDs, int lastProcessedQueueID) { BeatmapSetIDs = beatmapSetIDs; LastProcessedQueueID = lastProcessedQueueID; diff --git a/osu.Game/Online/Metadata/IMetadataServer.cs b/osu.Game/Online/Metadata/IMetadataServer.cs index edf288b6cf..994f60f877 100644 --- a/osu.Game/Online/Metadata/IMetadataServer.cs +++ b/osu.Game/Online/Metadata/IMetadataServer.cs @@ -16,6 +16,6 @@ namespace osu.Game.Online.Metadata /// /// The last processed queue ID. /// - Task GetChangesSince(uint queueId); + Task GetChangesSince(int queueId); } } diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs index 65817b3152..1e5eeb4eb0 100644 --- a/osu.Game/Online/Metadata/MetadataClient.cs +++ b/osu.Game/Online/Metadata/MetadataClient.cs @@ -10,6 +10,6 @@ namespace osu.Game.Online.Metadata { public abstract Task BeatmapSetsUpdated(BeatmapUpdates updates); - public abstract Task GetChangesSince(uint queueId); + public abstract Task GetChangesSince(int queueId); } } diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index b05efd5311..1b0d1884dc 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Online.API; namespace osu.Game.Online.Metadata @@ -19,7 +20,7 @@ namespace osu.Game.Online.Metadata private IHubClientConnector? connector; - private uint? lastQueueId; + private Bindable lastQueueId = null!; private HubConnection? connection => connector?.CurrentConnection; @@ -30,7 +31,7 @@ namespace osu.Game.Online.Metadata } [BackgroundDependencyLoader] - private void load(IAPIProvider api) + private void load(IAPIProvider api, OsuConfigManager config) { // Importantly, we are intentionally not using MessagePack here to correctly support derived class serialization. // More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code. @@ -47,6 +48,8 @@ namespace osu.Game.Online.Metadata connector.IsConnected.BindValueChanged(isConnectedChanged, true); } + + lastQueueId = config.GetBindable(OsuSetting.LastProcessedMetadataId); } private bool catchingUp; @@ -56,7 +59,7 @@ namespace osu.Game.Online.Metadata if (!connected.NewValue) return; - if (lastQueueId != null) + if (lastQueueId.Value >= 0) { catchingUp = true; @@ -69,7 +72,7 @@ namespace osu.Game.Online.Metadata Logger.Log($"Requesting catch-up from {lastQueueId.Value}"); var catchUpChanges = await GetChangesSince(lastQueueId.Value); - lastQueueId = catchUpChanges.LastProcessedQueueID; + lastQueueId.Value = catchUpChanges.LastProcessedQueueID; if (catchUpChanges.BeatmapSetIDs.Length == 0) { @@ -94,7 +97,7 @@ namespace osu.Game.Online.Metadata // If we're still catching up, avoid updating the last ID as it will interfere with catch-up efforts. if (!catchingUp) - lastQueueId = updates.LastProcessedQueueID; + lastQueueId.Value = updates.LastProcessedQueueID; await ProcessChanges(updates.BeatmapSetIDs); } @@ -110,7 +113,7 @@ namespace osu.Game.Online.Metadata return Task.CompletedTask; } - public override Task GetChangesSince(uint queueId) + public override Task GetChangesSince(int queueId) { if (connector?.IsConnected.Value != true) return Task.FromCanceled(default); From 99afbc7b73d76c68a41d96fd4fa1b5efe69d9ec5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Jul 2022 22:15:52 +0900 Subject: [PATCH 326/363] Add missing endpoint URLs --- osu.Game/Online/DevelopmentEndpointConfiguration.cs | 1 + osu.Game/Online/ProductionEndpointConfiguration.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game/Online/DevelopmentEndpointConfiguration.cs b/osu.Game/Online/DevelopmentEndpointConfiguration.cs index 83fd02512b..3171d15fc2 100644 --- a/osu.Game/Online/DevelopmentEndpointConfiguration.cs +++ b/osu.Game/Online/DevelopmentEndpointConfiguration.cs @@ -14,6 +14,7 @@ namespace osu.Game.Online APIClientID = "5"; SpectatorEndpointUrl = $"{APIEndpointUrl}/spectator"; MultiplayerEndpointUrl = $"{APIEndpointUrl}/multiplayer"; + MetadataEndpointUrl = $"{APIEndpointUrl}/metadata"; } } } diff --git a/osu.Game/Online/ProductionEndpointConfiguration.cs b/osu.Game/Online/ProductionEndpointConfiguration.cs index f431beac1c..316452280d 100644 --- a/osu.Game/Online/ProductionEndpointConfiguration.cs +++ b/osu.Game/Online/ProductionEndpointConfiguration.cs @@ -14,6 +14,7 @@ namespace osu.Game.Online APIClientID = "5"; SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer"; + MetadataEndpointUrl = "https://spectator.ppy.sh/metadata"; } } } From 3a68f386a8e7230ece6c402b0d98c9b8a3de24cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 01:25:04 +0900 Subject: [PATCH 327/363] Add forgotten password link to login form --- osu.Game/Overlays/Login/LoginForm.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index f545e2892f..b3e120f443 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -49,6 +49,7 @@ namespace osu.Game.Overlays.Login RelativeSizeAxes = Axes.X; ErrorTextFlowContainer errorText; + LinkFlowContainer forgottenPaswordLink; Children = new Drawable[] { @@ -80,6 +81,12 @@ namespace osu.Game.Overlays.Login LabelText = "Stay signed in", Current = config.GetBindable(OsuSetting.SavePassword), }, + forgottenPaswordLink = new LinkFlowContainer + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, new Container { RelativeSizeAxes = Axes.X, @@ -109,6 +116,8 @@ namespace osu.Game.Overlays.Login } }; + forgottenPaswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, "https://osu.ppy.sh/home/password-reset"); + password.OnCommit += (_, _) => performLogin(); if (api?.LastLoginError?.Message is string error) From 6fb00d84f997cfa318ae81636c064b8498d822b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 01:29:00 +0900 Subject: [PATCH 328/363] Apply NRT to `LoginForm` and remove nullability of `IAPIProvider` --- osu.Game/Overlays/Login/LoginForm.cs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index b3e120f443..0042f4607d 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; @@ -23,19 +21,19 @@ namespace osu.Game.Overlays.Login { public class LoginForm : FillFlowContainer { - private TextBox username; - private TextBox password; - private ShakeContainer shakeSignIn; + private TextBox username = null!; + private TextBox password = null!; + private ShakeContainer shakeSignIn = null!; - [Resolved(CanBeNull = true)] - private IAPIProvider api { get; set; } + [Resolved] + private IAPIProvider api { get; set; } = null!; - public Action RequestHide; + public Action? RequestHide; private void performLogin() { if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) - api?.Login(username.Text, password.Text); + api.Login(username.Text, password.Text); else shakeSignIn.Shake(); } @@ -57,7 +55,7 @@ namespace osu.Game.Overlays.Login { PlaceholderText = UsersStrings.LoginUsername.ToLower(), RelativeSizeAxes = Axes.X, - Text = api?.ProvidedUsername ?? string.Empty, + Text = api.ProvidedUsername, TabbableContentContainer = this }, password = new OsuPasswordTextBox @@ -110,17 +108,17 @@ namespace osu.Game.Overlays.Login Text = "Register", Action = () => { - RequestHide(); + RequestHide?.Invoke(); accountCreation.Show(); } } }; - forgottenPaswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, "https://osu.ppy.sh/home/password-reset"); + forgottenPaswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, $"{api.WebsiteRootUrl}/home/password-reset"); password.OnCommit += (_, _) => performLogin(); - if (api?.LastLoginError?.Message is string error) + if (api.LastLoginError?.Message is string error) errorText.AddErrors(new[] { error }); } From c95eb2d2c1c6b93e89e2a11755fe2895f583b611 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 14:28:47 +0900 Subject: [PATCH 329/363] Fix incorrect case in `CatcherArea` parameter --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index a0279b5c83..7a29ba9801 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -120,10 +120,10 @@ namespace osu.Game.Rulesets.Catch.UI lastHyperDashState = Catcher.HyperDashing; } - public void SetCatcherPosition(float X) + public void SetCatcherPosition(float x) { float lastPosition = Catcher.X; - float newPosition = Math.Clamp(X, 0, CatchPlayfield.WIDTH); + float newPosition = Math.Clamp(x, 0, CatchPlayfield.WIDTH); Catcher.X = newPosition; From a52ea3cabe34c6e1c90a6126ab5a506151ca6362 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 14:29:55 +0900 Subject: [PATCH 330/363] Enable NRT and simplify `LineBufferedReader` --- .../Beatmaps/IO/LineBufferedReaderTest.cs | 12 ++--- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- osu.Game/IO/LineBufferedReader.cs | 48 ++++++++----------- 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs index 8f20fd7a68..14fbcb6176 100644 --- a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs @@ -108,19 +108,13 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestReadToEndAfterReadsAndPeeks() { - const string contents = "this line is gone\rthis one shouldn't be\r\nthese ones\ndefinitely not"; + const string contents = "first line\r\nsecond line"; using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var bufferedReader = new LineBufferedReader(stream)) { - Assert.AreEqual("this line is gone", bufferedReader.ReadLine()); - Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine()); - - string[] endingLines = bufferedReader.ReadToEnd().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); - Assert.AreEqual(3, endingLines.Length); - Assert.AreEqual("this one shouldn't be", endingLines[0]); - Assert.AreEqual("these ones", endingLines[1]); - Assert.AreEqual("definitely not", endingLines[2]); + bufferedReader.PeekLine(); + Assert.Throws(() => bufferedReader.ReadToEnd()); } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index a5e6ac0a1c..52e760a068 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps.Formats { Section section = Section.General; - string line; + string? line; while ((line = stream.ReadLine()) != null) { diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index db435576bf..1e329c786f 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; -using System.Collections.Generic; using System.IO; using System.Text; @@ -17,58 +14,51 @@ namespace osu.Game.IO public class LineBufferedReader : IDisposable { private readonly StreamReader streamReader; - private readonly Queue lineBuffer; + + private string? peekedLine; public LineBufferedReader(Stream stream, bool leaveOpen = false) { streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen); - lineBuffer = new Queue(); } /// /// Reads the next line from the stream without consuming it. /// Subsequent calls to without a will return the same string. /// - public string PeekLine() - { - if (lineBuffer.Count > 0) - return lineBuffer.Peek(); - - string line = streamReader.ReadLine(); - if (line != null) - lineBuffer.Enqueue(line); - return line; - } + public string? PeekLine() => peekedLine ??= streamReader.ReadLine(); /// /// Reads the next line from the stream and consumes it. /// If a line was peeked, that same line will then be consumed and returned. /// - public string ReadLine() => lineBuffer.Count > 0 ? lineBuffer.Dequeue() : streamReader.ReadLine(); + public string? ReadLine() + { + try + { + return peekedLine ?? streamReader.ReadLine(); + } + finally + { + peekedLine = null; + } + } /// /// Reads the stream to its end and returns the text read. - /// This includes any peeked but unconsumed lines. + /// Not compatible with calls to . /// public string ReadToEnd() { - string remainingText = streamReader.ReadToEnd(); - if (lineBuffer.Count == 0) - return remainingText; + if (peekedLine != null) + throw new InvalidOperationException($"Do not use {nameof(ReadToEnd)} when also peeking for lines."); - var builder = new StringBuilder(); - - // this might not be completely correct due to varying platform line endings - while (lineBuffer.Count > 0) - builder.AppendLine(lineBuffer.Dequeue()); - builder.Append(remainingText); - - return builder.ToString(); + return streamReader.ReadToEnd(); } public void Dispose() { - streamReader?.Dispose(); + streamReader.Dispose(); } } } From ae49aafde215e65f19db42b2a73d2848a509ed0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 14:50:30 +0900 Subject: [PATCH 331/363] Fix `BeatSyncedContainer` unintentionally blocking on beatmap load --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 45a935d165..4b40add87f 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -86,7 +86,7 @@ namespace osu.Game.Graphics.Containers TimingControlPoint timingPoint; EffectControlPoint effectPoint; - IsBeatSyncedWithTrack = BeatSyncSource.Clock?.IsRunning == true; + IsBeatSyncedWithTrack = BeatSyncSource.Clock?.IsRunning == true && BeatSyncSource.ControlPoints != null; double currentTrackTime; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ead3eeb0dc..c6a32e1d5b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -583,7 +583,7 @@ namespace osu.Game Host.ExceptionThrown -= onExceptionThrown; } - ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.Beatmap.ControlPointInfo; + ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.BeatmapLoaded ? Beatmap.Value.Beatmap.ControlPointInfo : null; IClock IBeatSyncProvider.Clock => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track : (IClock)null; ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; } From 12d396a51359dcec41f24a34aa8424f43d5acc2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 15:43:35 +0900 Subject: [PATCH 332/363] Use `-1` to specify default buffer size --- osu.Game/IO/LineBufferedReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index 1e329c786f..0210d7207e 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -19,7 +19,7 @@ namespace osu.Game.IO public LineBufferedReader(Stream stream, bool leaveOpen = false) { - streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen); + streamReader = new StreamReader(stream, Encoding.UTF8, true, -1, leaveOpen); } /// From a1b6ec60c899fe564e563b45b712fec9616876ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 18:38:39 +0900 Subject: [PATCH 333/363] Add statistics display for `MemoryCachingComponent`s Never sure if these are working as they should (or how well they are working). This helps quite a bit. --- osu.Game/Database/MemoryCachingComponent.cs | 46 +++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game/Database/MemoryCachingComponent.cs b/osu.Game/Database/MemoryCachingComponent.cs index 104943bbae..571a9ccc7c 100644 --- a/osu.Game/Database/MemoryCachingComponent.cs +++ b/osu.Game/Database/MemoryCachingComponent.cs @@ -8,7 +8,9 @@ using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Statistics; namespace osu.Game.Database { @@ -20,8 +22,16 @@ namespace osu.Game.Database { private readonly ConcurrentDictionary cache = new ConcurrentDictionary(); + private readonly GlobalStatistic statistics; + protected virtual bool CacheNullValues => true; + protected MemoryCachingComponent() + { + statistics = GlobalStatistics.Get(nameof(MemoryCachingComponent), GetType().ReadableName()); + statistics.Value = new MemoryCachingStatistics(); + } + /// /// Retrieve the cached value for the given lookup. /// @@ -30,12 +40,20 @@ namespace osu.Game.Database protected async Task GetAsync([NotNull] TLookup lookup, CancellationToken token = default) { if (CheckExists(lookup, out TValue performance)) + { + statistics.Value.HitCount++; return performance; + } var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false); + statistics.Value.MissCount++; + if (computed != null || CacheNullValues) + { cache[lookup] = computed; + statistics.Value.Usage = cache.Count; + } return computed; } @@ -51,6 +69,8 @@ namespace osu.Game.Database if (matchKeyPredicate(kvp.Key)) cache.TryRemove(kvp.Key, out _); } + + statistics.Value.Usage = cache.Count; } protected bool CheckExists([NotNull] TLookup lookup, out TValue value) => @@ -63,5 +83,31 @@ namespace osu.Game.Database /// An optional to cancel the operation. /// The computed value. protected abstract Task ComputeValueAsync(TLookup lookup, CancellationToken token = default); + + private class MemoryCachingStatistics + { + /// + /// Total number of cache hits. + /// + public int HitCount; + + /// + /// Total number of cache misses. + /// + public int MissCount; + + /// + /// Total number of cached entities. + /// + public int Usage; + + public override string ToString() + { + int totalAccesses = HitCount + MissCount; + double hitRate = totalAccesses == 0 ? 0 : (double)HitCount / totalAccesses; + + return $"i:{Usage} h:{HitCount} m:{MissCount} {hitRate:0%}"; + } + } } } From 01bc6e5cb7ef98d26e001b8f89bef14fd1ddabd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 19:53:19 +0900 Subject: [PATCH 334/363] Revert old behaviour of `ReadToEnd` --- .../Beatmaps/IO/LineBufferedReaderTest.cs | 12 +++++++++--- osu.Game/IO/LineBufferedReader.cs | 15 +++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs index 14fbcb6176..8f20fd7a68 100644 --- a/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LineBufferedReaderTest.cs @@ -108,13 +108,19 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestReadToEndAfterReadsAndPeeks() { - const string contents = "first line\r\nsecond line"; + const string contents = "this line is gone\rthis one shouldn't be\r\nthese ones\ndefinitely not"; using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var bufferedReader = new LineBufferedReader(stream)) { - bufferedReader.PeekLine(); - Assert.Throws(() => bufferedReader.ReadToEnd()); + Assert.AreEqual("this line is gone", bufferedReader.ReadLine()); + Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine()); + + string[] endingLines = bufferedReader.ReadToEnd().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + Assert.AreEqual(3, endingLines.Length); + Assert.AreEqual("this one shouldn't be", endingLines[0]); + Assert.AreEqual("these ones", endingLines[1]); + Assert.AreEqual("definitely not", endingLines[2]); } } } diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index 0210d7207e..6fcea08990 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -46,14 +46,21 @@ namespace osu.Game.IO /// /// Reads the stream to its end and returns the text read. - /// Not compatible with calls to . + /// This includes any peeked but unconsumed lines. /// public string ReadToEnd() { - if (peekedLine != null) - throw new InvalidOperationException($"Do not use {nameof(ReadToEnd)} when also peeking for lines."); + string remainingText = streamReader.ReadToEnd(); + if (peekedLine == null) + return remainingText; - return streamReader.ReadToEnd(); + var builder = new StringBuilder(); + + // this might not be completely correct due to varying platform line endings + builder.AppendLine(peekedLine); + builder.Append(remainingText); + + return builder.ToString(); } public void Dispose() From c2f106907364de79167a92cf8e0f2f1930168449 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 19:55:43 +0900 Subject: [PATCH 335/363] Avoid usage of `finally` in potentially hot path --- osu.Game/IO/LineBufferedReader.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index 6fcea08990..93e554b43d 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -34,14 +34,10 @@ namespace osu.Game.IO /// public string? ReadLine() { - try - { - return peekedLine ?? streamReader.ReadLine(); - } - finally - { - peekedLine = null; - } + string? line = peekedLine ?? streamReader.ReadLine(); + + peekedLine = null; + return line; } /// From 7f94405c9e4ea5bc81416b270cb9cce22a1cc4b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Jul 2022 23:38:11 +0300 Subject: [PATCH 336/363] Rename method and make duration optional --- .../Skinning/Default/DefaultSpinnerDisc.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs index 03db76336c..60489c1b22 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { base.LoadComplete(); - complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); + complete.BindValueChanged(complete => updateDiscColour(complete.NewValue, 200)); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default this.ScaleTo(initial_scale); this.RotateTo(0); - updateComplete(false, 0); + updateDiscColour(false); using (BeginDelayedSequence(spinner.TimePreempt / 2)) { @@ -182,11 +182,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default if (drawableSpinner.Result?.TimeCompleted is double completionTime) { using (BeginAbsoluteSequence(completionTime)) - updateComplete(true, 200); + updateDiscColour(true, 200); } } - private void updateComplete(bool complete, double duration) + private void updateDiscColour(bool complete, double duration = 0) { var colour = complete ? completeColour : normalColour; From a5b01b89201ef4ba5db1fa64f370d180aa80d567 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Jul 2022 01:00:42 +0300 Subject: [PATCH 337/363] Improve asserts in `TestSeekPerformsInGameplayTime` to be more descriptive --- .../Gameplay/TestSceneMasterGameplayClockContainer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index ae431e77ae..0395ae9d99 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -9,7 +9,6 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; @@ -116,10 +115,10 @@ namespace osu.Game.Tests.Gameplay AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500)); - AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f)); + AddStep("gameplay clock time = 2500", () => Assert.AreEqual(gameplayClockContainer.CurrentTime, 2500, 10f)); AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000)); - AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 10000, 10f)); + AddStep("gameplay clock time = 10000", () => Assert.AreEqual(gameplayClockContainer.CurrentTime, 10000, 10f)); } protected override void Dispose(bool isDisposing) From 911507291788745f1fc90f4824455b2698408186 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Jul 2022 04:24:10 +0300 Subject: [PATCH 338/363] Fix flaky tests not running at all with environment variable set --- osu.Game/Tests/FlakyTestAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/FlakyTestAttribute.cs b/osu.Game/Tests/FlakyTestAttribute.cs index 299dbb89a2..c61ce80bf5 100644 --- a/osu.Game/Tests/FlakyTestAttribute.cs +++ b/osu.Game/Tests/FlakyTestAttribute.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests } public FlakyTestAttribute(int tryCount) - : base(Environment.GetEnvironmentVariable("OSU_TESTS_FAIL_FLAKY") == "1" ? 0 : tryCount) + : base(Environment.GetEnvironmentVariable("OSU_TESTS_FAIL_FLAKY") == "1" ? 1 : tryCount) { } } From c4b6893709c15ca2aace28c40697a3ed30555911 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 14:29:15 +0900 Subject: [PATCH 339/363] Add local handling of cases where a beatmap's file cannot be found on disk --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 76 ++++++++++++++++++++---- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 9d31c58709..088cbd9d60 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -137,8 +137,17 @@ namespace osu.Game.Beatmaps try { - using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path)))) - return Decoder.GetDecoder(stream).Decode(stream); + string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path); + var stream = GetStream(fileStorePath); + + if (stream == null) + { + Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error); + return null; + } + + using (var reader = new LineBufferedReader(stream)) + return Decoder.GetDecoder(reader).Decode(reader); } catch (Exception e) { @@ -154,7 +163,16 @@ namespace osu.Game.Beatmaps try { - return resources.LargeTextureStore.Get(BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile)); + string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile); + var texture = resources.LargeTextureStore.Get(fileStorePath); + + if (texture == null) + { + Logger.Log($"Beatmap background failed to load (file {Metadata.BackgroundFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error); + return null; + } + + return texture; } catch (Exception e) { @@ -173,7 +191,16 @@ namespace osu.Game.Beatmaps try { - return resources.Tracks.Get(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile)); + string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.AudioFile); + var track = resources.Tracks.Get(fileStorePath); + + if (track == null) + { + Logger.Log($"Beatmap failed to load (file {Metadata.AudioFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error); + return null; + } + + return track; } catch (Exception e) { @@ -192,8 +219,17 @@ namespace osu.Game.Beatmaps try { - var trackData = GetStream(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile)); - return trackData == null ? null : new Waveform(trackData); + string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.AudioFile); + + var trackData = GetStream(fileStorePath); + + if (trackData == null) + { + Logger.Log($"Beatmap waveform failed to load (file {Metadata.AudioFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error); + return null; + } + + return new Waveform(trackData); } catch (Exception e) { @@ -211,19 +247,37 @@ namespace osu.Game.Beatmaps try { - using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path)))) + string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path); + var stream = GetStream(fileStorePath); + + if (stream == null) { - var decoder = Decoder.GetDecoder(stream); + Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath})", level: LogLevel.Error); + return null; + } + + using (var reader = new LineBufferedReader(stream)) + { + var decoder = Decoder.GetDecoder(reader); string storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; // todo: support loading from both set-wide storyboard *and* beatmap specific. if (string.IsNullOrEmpty(storyboardFilename)) - storyboard = decoder.Decode(stream); + storyboard = decoder.Decode(reader); else { - using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(storyboardFilename)))) - storyboard = decoder.Decode(stream, secondaryStream); + string storyboardFileStorePath = BeatmapSetInfo.GetPathForFile(storyboardFilename); + var secondaryStream = GetStream(storyboardFileStorePath); + + if (secondaryStream == null) + { + Logger.Log($"Storyboard failed to load (file {storyboardFilename} not found on disk at expected location {fileStorePath})", level: LogLevel.Error); + return null; + } + + using (var secondaryReader = new LineBufferedReader(secondaryStream)) + storyboard = decoder.Decode(reader, secondaryReader); } } } From e81cebf27d4a5030cce1d2550e9895515b9acfdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 14:33:17 +0900 Subject: [PATCH 340/363] Change storyboard parsing logic to not completely fail if only `.osb` read fails Changes to allow the storyboard to exist if only the `.osu` is available. Reads better IMO. --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 088cbd9d60..ce883a7092 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -248,37 +248,37 @@ namespace osu.Game.Beatmaps try { string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path); - var stream = GetStream(fileStorePath); + var beatmapFileStream = GetStream(fileStorePath); - if (stream == null) + if (beatmapFileStream == null) { Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath})", level: LogLevel.Error); return null; } - using (var reader = new LineBufferedReader(stream)) + using (var reader = new LineBufferedReader(beatmapFileStream)) { var decoder = Decoder.GetDecoder(reader); - string storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; + Stream storyboardFileStream = null; - // todo: support loading from both set-wide storyboard *and* beatmap specific. - if (string.IsNullOrEmpty(storyboardFilename)) - storyboard = decoder.Decode(reader); - else + if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename is string storyboardFilename) { - string storyboardFileStorePath = BeatmapSetInfo.GetPathForFile(storyboardFilename); - var secondaryStream = GetStream(storyboardFileStorePath); + string storyboardFileStorePath = BeatmapSetInfo?.GetPathForFile(storyboardFilename); + storyboardFileStream = GetStream(storyboardFileStorePath); - if (secondaryStream == null) - { - Logger.Log($"Storyboard failed to load (file {storyboardFilename} not found on disk at expected location {fileStorePath})", level: LogLevel.Error); - return null; - } + if (storyboardFileStream == null) + Logger.Log($"Storyboard failed to load (file {storyboardFilename} not found on disk at expected location {storyboardFileStorePath})", level: LogLevel.Error); + } - using (var secondaryReader = new LineBufferedReader(secondaryStream)) + if (storyboardFileStream != null) + { + // Stand-alone storyboard was found, so parse in addition to the beatmap's local storyboard. + using (var secondaryReader = new LineBufferedReader(storyboardFileStream)) storyboard = decoder.Decode(reader, secondaryReader); } + else + storyboard = decoder.Decode(reader); } } catch (Exception e) From e1b434b5dc3b685dc55b2d806fa8fb72fd852ee7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 14:46:51 +0900 Subject: [PATCH 341/363] Fix song select placeholder not showing convert hint for custom rulesets --- osu.Game/Screens/Select/NoResultsPlaceholder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index 5d5eafd2e6..b8b589ff99 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Select // TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch). // TODO: Make this message more certain by ensuring the osu! beatmaps exist before suggesting. - if (filter?.Ruleset?.OnlineID > 0 && !filter.AllowConvertedBeatmaps) + if (filter?.Ruleset?.OnlineID != 0 && filter?.AllowConvertedBeatmaps == false) { textFlow.AddParagraph("- Try"); textFlow.AddLink(" enabling ", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); From 45c5b7e7dd4061157195b9f4f5a1a5fb388467f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:13:16 +0900 Subject: [PATCH 342/363] 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 398c1d91dd..8c15ed7949 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 ed6577651a..06b022ea44 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 7d70b96f62..9085205bbc 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 7ef03dd2cbe7b723d09ae898f8bced3d13812d2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Jul 2022 16:32:53 +0900 Subject: [PATCH 343/363] Use fire-and-forget async operations on global track This avoids any blocking overhead caused by a backlogged audio thread. Test seem to pass so might be okay? Note that order is still guaranteed due to the `ensureUpdateThread` queueing system framework-side. --- osu.Game/Overlays/MusicController.cs | 8 ++++---- osu.Game/Tests/Visual/OsuTestScene.cs | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 4a10f30a7a..8af295dfe8 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -133,9 +133,9 @@ namespace osu.Game.Overlays UserPauseRequested = false; if (restart) - CurrentTrack.Restart(); + CurrentTrack.RestartAsync(); else if (!IsPlaying) - CurrentTrack.Start(); + CurrentTrack.StartAsync(); return true; } @@ -152,7 +152,7 @@ namespace osu.Game.Overlays { UserPauseRequested |= requestedByUser; if (CurrentTrack.IsRunning) - CurrentTrack.Stop(); + CurrentTrack.StopAsync(); } /// @@ -250,7 +250,7 @@ namespace osu.Game.Overlays { // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). // we probably want to move this to a central method for switching to a new working beatmap in the future. - Schedule(() => CurrentTrack.Restart()); + Schedule(() => CurrentTrack.RestartAsync()); } private WorkingBeatmap current; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index c13cdff820..012c512266 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -430,11 +430,19 @@ namespace osu.Game.Tests.Visual return accumulated == seek; } + public override Task SeekAsync(double seek) => Task.FromResult(Seek(seek)); + public override void Start() { running = true; } + public override Task StartAsync() + { + Start(); + return Task.CompletedTask; + } + public override void Reset() { Seek(0); @@ -450,6 +458,12 @@ namespace osu.Game.Tests.Visual } } + public override Task StopAsync() + { + Stop(); + return Task.CompletedTask; + } + public override bool IsRunning => running; private double? lastReferenceTime; From 5197d0fa9e1afbc25f004b56413121a9637b386a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:32:48 +0900 Subject: [PATCH 344/363] Add automatic transaction handling to realm helper methods --- osu.Game.Tests/Database/RealmLiveTests.cs | 19 ++++++++++++ osu.Game/Database/RealmExtensions.cs | 35 ++++++++++++++++++++--- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index a50eb22c67..d15e038723 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -59,6 +59,25 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestNestedWriteCalls() + { + RunTestWithRealm((realm, _) => + { + var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); + + var liveBeatmap = beatmap.ToLive(realm); + + realm.Run(r => + r.Write(_ => + r.Write(_ => + r.Add(beatmap))) + ); + + Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden)); + }); + } + [Test] public void TestAccessAfterAttach() { diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index 73e9f16d33..2cd81b6af1 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.cs @@ -8,18 +8,45 @@ namespace osu.Game.Database { public static class RealmExtensions { + /// + /// Perform a write operation against the provided realm instance. + /// + /// + /// This will automatically start a transaction if not already in one. + /// + /// The realm to operate on. + /// The write operation to run. public static void Write(this Realm realm, Action function) { - using var transaction = realm.BeginWrite(); + Transaction? transaction = null; + + if (!realm.IsInTransaction) + transaction = realm.BeginWrite(); + function(realm); - transaction.Commit(); + + transaction?.Commit(); } + /// + /// Perform a write operation against the provided realm instance. + /// + /// + /// This will automatically start a transaction if not already in one. + /// + /// The realm to operate on. + /// The write operation to run. public static T Write(this Realm realm, Func function) { - using var transaction = realm.BeginWrite(); + Transaction? transaction = null; + + if (!realm.IsInTransaction) + transaction = realm.BeginWrite(); + var result = function(realm); - transaction.Commit(); + + transaction?.Commit(); + return result; } From e2c4c94993e61234c42b08d359681ce12df39fa0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:37:46 +0900 Subject: [PATCH 345/363] Simplify `BeatmapUpdater` transaction handling using nested transaction support --- osu.Game/Beatmaps/BeatmapUpdater.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index d0ec44a034..978c6de35c 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -52,15 +52,7 @@ namespace osu.Game.Beatmaps /// /// Run all processing on a beatmap immediately. /// - public void Process(BeatmapSetInfo beatmapSet) - { - if (beatmapSet.Realm.IsInTransaction) - Process(beatmapSet, beatmapSet.Realm); - else - beatmapSet.Realm.Write(r => Process(beatmapSet, r)); - } - - public void Process(BeatmapSetInfo beatmapSet, Realm realm) + public void Process(BeatmapSetInfo beatmapSet) => beatmapSet.Realm.Write(r => { // Before we use below, we want to invalidate. workingBeatmapCache.Invalidate(beatmapSet); @@ -85,7 +77,7 @@ namespace osu.Game.Beatmaps // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. workingBeatmapCache.Invalidate(beatmapSet); - } + }); private double calculateLength(IBeatmap b) { From dd5b127fb5e3e742053ff17b3965b4ecabd1bcc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:51:49 +0900 Subject: [PATCH 346/363] Update various tests to enable NRT to avoid new inspection failures --- .../Visual/Editing/TestSceneComposeScreen.cs | 4 +-- .../Editing/TestSceneEditorBeatmapCreation.cs | 34 +++++++++---------- .../TestSceneMultiSpectatorScreen.cs | 21 ++++++------ .../Multiplayer/TestSceneMultiplayer.cs | 24 ++++++------- .../TestSceneMultiplayerParticipantsList.cs | 10 +++--- .../Online/TestSceneLeaderboardModSelector.cs | 4 +-- 6 files changed, 44 insertions(+), 53 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 6b5d9af7af..291630fa3a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -24,7 +22,7 @@ namespace osu.Game.Tests.Visual.Editing [TestFixture] public class TestSceneComposeScreen : EditorClockTestScene { - private EditorBeatmap editorBeatmap; + private EditorBeatmap editorBeatmap = null!; [Cached] private EditorClipboard clipboard = new EditorClipboard(); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 2707682b4c..f565ca3ef4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.IO; using System.Linq; @@ -39,7 +37,9 @@ namespace osu.Game.Tests.Visual.Editing protected override bool IsolateSavingFromDatabase => false; [Resolved] - private BeatmapManager beatmapManager { get; set; } + private BeatmapManager beatmapManager { get; set; } = null!; + + private Guid currentBeatmapSetID => EditorBeatmap.BeatmapInfo.BeatmapSet?.ID ?? Guid.Empty; public override void SetUpSteps() { @@ -50,19 +50,19 @@ namespace osu.Game.Tests.Visual.Editing AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); } - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null); + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => new DummyWorkingBeatmap(Audio, null); [Test] public void TestCreateNewBeatmap() { AddStep("save beatmap", () => Editor.Save()); - AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.Value.DeletePending == false); + AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID)?.Value.DeletePending == false); } [Test] public void TestExitWithoutSave() { - EditorBeatmap editorBeatmap = null; + EditorBeatmap editorBeatmap = null!; AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap); @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.Value.DeletePending == true); + AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.AsNonNull().ID)?.Value.DeletePending == true); } [Test] @@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new beatmap persisted", () => { var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName); - var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID); return beatmap != null && beatmap.DifficultyName == firstDifficultyName @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for created", () => { - string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; return difficultyName != null && difficultyName != firstDifficultyName; }); @@ -195,7 +195,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new beatmap persisted", () => { var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); - var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID); return beatmap != null && beatmap.DifficultyName == secondDifficultyName @@ -246,7 +246,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new beatmap persisted", () => { var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == originalDifficultyName); - var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID); return beatmap != null && beatmap.DifficultyName == originalDifficultyName @@ -262,7 +262,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for created", () => { - string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; return difficultyName != null && difficultyName != originalDifficultyName; }); @@ -281,13 +281,13 @@ namespace osu.Game.Tests.Visual.Editing AddStep("save beatmap", () => Editor.Save()); - BeatmapInfo refetchedBeatmap = null; - Live refetchedBeatmapSet = null; + BeatmapInfo? refetchedBeatmap = null; + Live? refetchedBeatmapSet = null; AddStep("refetch from database", () => { refetchedBeatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == copyDifficultyName); - refetchedBeatmapSet = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + refetchedBeatmapSet = beatmapManager.QueryBeatmapSet(s => s.ID == currentBeatmapSetID); }); AddAssert("new beatmap persisted", () => @@ -323,7 +323,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for created", () => { - string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; return difficultyName != null && difficultyName != "New Difficulty"; }); AddAssert("new difficulty has correct name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "New Difficulty (1)"); @@ -359,7 +359,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for created", () => { - string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; return difficultyName != null && difficultyName != duplicate_difficulty_name; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 877c986d61..7df68392cf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -33,20 +31,21 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiSpectatorScreen : MultiplayerTestScene { [Resolved] - private OsuGameBase game { get; set; } + private OsuGameBase game { get; set; } = null!; [Resolved] - private OsuConfigManager config { get; set; } + private OsuConfigManager config { get; set; } = null!; [Resolved] - private BeatmapManager beatmapManager { get; set; } + private BeatmapManager beatmapManager { get; set; } = null!; - private MultiSpectatorScreen spectatorScreen; + private MultiSpectatorScreen spectatorScreen = null!; private readonly List playingUsers = new List(); - private BeatmapSetInfo importedSet; - private BeatmapInfo importedBeatmap; + private BeatmapSetInfo importedSet = null!; + private BeatmapInfo importedBeatmap = null!; + private int importedBeatmapId; [BackgroundDependencyLoader] @@ -340,7 +339,7 @@ namespace osu.Game.Tests.Visual.Multiplayer sendFrames(getPlayerIds(count), 300); } - Player player = null; + Player? player = null; AddStep($"get {PLAYER_1_ID} player instance", () => player = getInstance(PLAYER_1_ID).ChildrenOfType().Single()); @@ -369,7 +368,7 @@ namespace osu.Game.Tests.Visual.Multiplayer b.Storyboard.GetLayer("Background").Add(sprite); }); - private void testLeadIn(Action applyToBeatmap = null) + private void testLeadIn(Action? applyToBeatmap = null) { start(PLAYER_1_ID); @@ -387,7 +386,7 @@ namespace osu.Game.Tests.Visual.Multiplayer assertRunning(PLAYER_1_ID); } - private void loadSpectateScreen(bool waitForPlayerLoad = true, Action applyToBeatmap = null) + private void loadSpectateScreen(bool waitForPlayerLoad = true, Action? applyToBeatmap = null) { AddStep("load screen", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index da48fb7332..a2793acba7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; using System.Linq; @@ -51,17 +49,17 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayer : ScreenTestScene { - private BeatmapManager beatmaps; - private RulesetStore rulesets; - private BeatmapSetInfo importedSet; + private BeatmapManager beatmaps = null!; + private RulesetStore rulesets = null!; + private BeatmapSetInfo importedSet = null!; - private TestMultiplayerComponents multiplayerComponents; + private TestMultiplayerComponents multiplayerComponents = null!; private TestMultiplayerClient multiplayerClient => multiplayerComponents.MultiplayerClient; private TestMultiplayerRoomManager roomManager => multiplayerComponents.RoomManager; [Resolved] - private OsuConfigManager config { get; set; } + private OsuConfigManager config { get; set; } = null!; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -146,7 +144,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void removeLastUser() { - APIUser lastUser = multiplayerClient.ServerRoom?.Users.Last().User; + APIUser? lastUser = multiplayerClient.ServerRoom?.Users.Last().User; if (lastUser == null || lastUser == multiplayerClient.LocalUser?.User) return; @@ -156,7 +154,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void kickLastUser() { - APIUser lastUser = multiplayerClient.ServerRoom?.Users.Last().User; + APIUser? lastUser = multiplayerClient.ServerRoom?.Users.Last().User; if (lastUser == null || lastUser == multiplayerClient.LocalUser?.User) return; @@ -351,7 +349,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select room", () => InputManager.Key(Key.Down)); AddStep("join room", () => InputManager.Key(Key.Enter)); - DrawableLoungeRoom.PasswordEntryPopover passwordEntryPopover = null; + DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null; AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType().FirstOrDefault()) != null); AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType().First().Text = "password"); AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType().First().TriggerClick()); @@ -678,7 +676,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestGameplayExitFlow() { - Bindable holdDelay = null; + Bindable? holdDelay = null; AddStep("Set hold delay to zero", () => { @@ -709,7 +707,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for lounge", () => multiplayerComponents.CurrentScreen is Screens.OnlinePlay.Multiplayer.Multiplayer); AddStep("stop holding", () => InputManager.ReleaseKey(Key.Escape)); - AddStep("set hold delay to default", () => holdDelay.SetDefault()); + AddStep("set hold delay to default", () => holdDelay?.SetDefault()); } [Test] @@ -992,7 +990,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for ready button to be enabled", () => readyButton.Enabled.Value); MultiplayerUserState lastState = MultiplayerUserState.Idle; - MultiplayerRoomUser user = null; + MultiplayerRoomUser? user = null; AddStep("click ready button", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 7db18d1127..a70dfd78c5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -66,7 +64,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestRemoveUser() { - APIUser secondUser = null; + APIUser? secondUser = null; AddStep("add a user", () => { @@ -80,7 +78,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("remove host", () => MultiplayerClient.RemoveUser(API.LocalUser.Value)); - AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.UserID == secondUser.Id); + AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.UserID == secondUser?.Id); } [Test] @@ -368,7 +366,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createNewParticipantsList() { - ParticipantsList participantsList = null; + ParticipantsList? participantsList = null; AddStep("create new list", () => Child = participantsList = new ParticipantsList { @@ -378,7 +376,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Size = new Vector2(380, 0.7f) }); - AddUntilStep("wait for list to load", () => participantsList.IsLoaded); + AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true); } private void checkProgressBarVisibility(bool visible) => diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index 8ab8276b9c..10d9a5664e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Overlays.BeatmapSet; using System.Collections.Specialized; using System.Linq; @@ -29,7 +27,7 @@ namespace osu.Game.Tests.Visual.Online LeaderboardModSelector modSelector; FillFlowContainer selectedMods; - var ruleset = new Bindable(); + var ruleset = new Bindable(); Add(selectedMods = new FillFlowContainer { From b5c703b62cb409adb2dad3adba5e751af4f8a965 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:59:55 +0900 Subject: [PATCH 347/363] Remove unused using statement --- osu.Game/Beatmaps/BeatmapUpdater.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 978c6de35c..20fa0bc7c6 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -10,7 +10,6 @@ using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Rulesets.Objects; -using Realms; namespace osu.Game.Beatmaps { From ac216d94a81db5e896b13cebf997b0d00d50c986 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 18:15:15 +0900 Subject: [PATCH 348/363] Fix transaction not being disposed --- osu.Game.Tests/Database/RealmLiveTests.cs | 18 +++++++++++++ osu.Game/Database/RealmExtensions.cs | 32 ++++++++++++++++------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index d15e038723..aec8c0b1e1 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -59,6 +59,24 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestFailedWritePerformsRollback() + { + RunTestWithRealm((realm, _) => + { + Assert.Throws(() => + { + realm.Write(r => + { + r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())); + throw new InvalidOperationException(); + }); + }); + + Assert.That(realm.Run(r => r.All()), Is.Empty); + }); + } + [Test] public void TestNestedWriteCalls() { diff --git a/osu.Game/Database/RealmExtensions.cs b/osu.Game/Database/RealmExtensions.cs index 2cd81b6af1..13c4defb83 100644 --- a/osu.Game/Database/RealmExtensions.cs +++ b/osu.Game/Database/RealmExtensions.cs @@ -20,12 +20,19 @@ namespace osu.Game.Database { Transaction? transaction = null; - if (!realm.IsInTransaction) - transaction = realm.BeginWrite(); + try + { + if (!realm.IsInTransaction) + transaction = realm.BeginWrite(); - function(realm); + function(realm); - transaction?.Commit(); + transaction?.Commit(); + } + finally + { + transaction?.Dispose(); + } } /// @@ -40,14 +47,21 @@ namespace osu.Game.Database { Transaction? transaction = null; - if (!realm.IsInTransaction) - transaction = realm.BeginWrite(); + try + { + if (!realm.IsInTransaction) + transaction = realm.BeginWrite(); - var result = function(realm); + var result = function(realm); - transaction?.Commit(); + transaction?.Commit(); - return result; + return result; + } + finally + { + transaction?.Dispose(); + } } /// From bf10f2db2ed2148ba47e6652c467e4c341a6d654 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 18:19:01 +0900 Subject: [PATCH 349/363] Add test coverage of nested rollback for good measure --- osu.Game.Tests/Database/RealmLiveTests.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index aec8c0b1e1..3615cebe6a 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -77,6 +77,27 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestFailedNestedWritePerformsRollback() + { + RunTestWithRealm((realm, _) => + { + Assert.Throws(() => + { + realm.Write(r => + { + realm.Write(_ => + { + r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())); + throw new InvalidOperationException(); + }); + }); + }); + + Assert.That(realm.Run(r => r.All()), Is.Empty); + }); + } + [Test] public void TestNestedWriteCalls() { From d88fd8a5b08201ffd44cd1349302bb850b69789c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 18:26:04 +0900 Subject: [PATCH 350/363] Allow searching for "skins" to find current skin setting --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 741b6b5815..d23ef7e3e7 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -62,7 +62,8 @@ namespace osu.Game.Overlays.Settings.Sections { skinDropdown = new SkinSettingsDropdown { - LabelText = SkinSettingsStrings.CurrentSkin + LabelText = SkinSettingsStrings.CurrentSkin, + Keywords = new[] { @"skins" } }, new SettingsButton { From cf1da1dd18bc7d9e3824acae4c94b53f45eebd4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 22:38:54 +0900 Subject: [PATCH 351/363] Fix skins potentially being duplicated on batch import Resolves https://github.com/ppy/osu/discussions/19024#discussioncomment-3099200 --- osu.Game/Database/RealmArchiveModelImporter.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 76f6db1384..ce3d0652b3 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -258,15 +258,13 @@ namespace osu.Game.Database { cancellationToken.ThrowIfCancellationRequested(); - bool checkedExisting = false; - TModel? existing = null; + TModel? existing; if (batchImport && archive != null) { // this is a fast bail condition to improve large import performance. item.Hash = computeHashFast(archive); - checkedExisting = true; existing = CheckForExisting(item, realm); if (existing != null) @@ -311,8 +309,12 @@ namespace osu.Game.Database // TODO: we may want to run this outside of the transaction. Populate(item, archive, realm, cancellationToken); - if (!checkedExisting) - existing = CheckForExisting(item, realm); + // Populate() may have adjusted file content (see SkinImporter.updateSkinIniMetadata), so regardless of whether a fast check was done earlier, let's + // check for existing items a second time. + // + // If this is ever a performance issue, the fast-check hash can be compared and trigger a skip of this second check if it still matches. + // I don't think it is a huge deal doing a second indexed check, though. + existing = CheckForExisting(item, realm); if (existing != null) { From cd4755fbd9151bde922444827d63550cdfe7b9bd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Jul 2022 18:06:32 +0300 Subject: [PATCH 352/363] Add test coverage for batch-import path --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 8b7fcae1a9..c3c10215a5 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -83,20 +83,20 @@ namespace osu.Game.Tests.Skins.IO #region Cases where imports should match existing [Test] - public Task TestImportTwiceWithSameMetadataAndFilename() => runSkinTest(async osu => + public Task TestImportTwiceWithSameMetadataAndFilename([Values] bool batchImport) => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); - var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"), batchImport); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"), batchImport); assertImportedOnce(import1, import2); }); [Test] - public Task TestImportTwiceWithNoMetadataSameDownloadFilename() => runSkinTest(async osu => + public Task TestImportTwiceWithNoMetadataSameDownloadFilename([Values] bool batchImport) => runSkinTest(async osu => { // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. - var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk")); - var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk"), batchImport); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk"), batchImport); assertImportedOnce(import1, import2); }); @@ -134,10 +134,10 @@ namespace osu.Game.Tests.Skins.IO }); [Test] - public Task TestSameMetadataNameSameFolderName() => runSkinTest(async osu => + public Task TestSameMetadataNameSameFolderName([Values] bool batchImport) => runSkinTest(async osu => { - var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1")); - var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1")); + var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"), batchImport); + var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"), batchImport); assertImportedOnce(import1, import2); assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu); @@ -357,10 +357,10 @@ namespace osu.Game.Tests.Skins.IO } } - private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import) + private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import, bool batchImport = false) { var skinManager = osu.Dependencies.Get(); - return await skinManager.Import(import); + return await skinManager.Import(import, batchImport); } } } From f500d5ade6d613376502ade4bee55fa179301092 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 01:06:39 +0900 Subject: [PATCH 353/363] Simplify error output when hub cannot connect Full call stack is useless in these cases. Before: ```csharp [network] 2022-07-07 16:05:31 [verbose]: OnlineMetadataClient connection error: System.Net.Http.HttpRequestException: Response status code does not indicate success: 403 (Forbidden). [network] 2022-07-07 16:05:31 [verbose]: at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.NegotiateAsync(Uri url, HttpClient httpClient, ILogger logger, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.GetNegotiationResponseAsync(Uri uri, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.SelectAndStartTransport(TransferFormat transferFormat, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsyncCore(TransferFormat transferFormat, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnection.StartAsync(TransferFormat transferFormat, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.Http.Connections.Client.HttpConnectionFactory.ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncCore(CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncInner(CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsync(CancellationToken cancellationToken) [network] 2022-07-07 16:05:31 [verbose]: at osu.Game.Online.HubClientConnector.connect() in /Users/dean/Projects/osu/osu.Game/Online/HubClientConnector.cs:line 119 ``` After: ```csharp [network] 2022-07-07 16:06:59 [verbose]: OnlineMetadataClient connecting... [network] 2022-07-07 16:06:59 [verbose]: OnlineMetadataClient connect attempt failed: Response status code does not indicate success: 403 (Forbidden). ``` --- osu.Game/Online/HubClientConnector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 61e9eaa8c0..01f0f3a902 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -144,7 +144,7 @@ namespace osu.Game.Online /// private async Task handleErrorAndDelay(Exception exception, CancellationToken cancellationToken) { - Logger.Log($"{clientName} connection error: {exception}", LoggingTarget.Network); + Logger.Log($"{clientName} connect attempt failed: {exception.Message}", LoggingTarget.Network); await Task.Delay(5000, cancellationToken).ConfigureAwait(false); } From b83073c2e91b482a035705faf4d7d397330215e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 01:37:43 +0900 Subject: [PATCH 354/363] Fix `SeasonalBackgroundLoader` triggering a background reload when not providing backgrounds --- osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index f2caf10e91..bfea3a722c 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -41,7 +41,11 @@ namespace osu.Game.Graphics.Backgrounds seasonalBackgroundMode.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke()); seasonalBackgrounds = sessionStatics.GetBindable(Static.SeasonalBackgrounds); - seasonalBackgrounds.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke()); + seasonalBackgrounds.BindValueChanged(response => + { + if (response.NewValue?.Backgrounds?.Count > 0) + SeasonalBackgroundChanged?.Invoke(); + }); apiState.BindTo(api.State); apiState.BindValueChanged(fetchSeasonalBackgrounds, true); From 789904ccd1cddc31fcf650f3793248f3533520c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 01:39:09 +0900 Subject: [PATCH 355/363] Avoid reloading background unnecessariyl when not yet loaded --- .../Backgrounds/BackgroundScreenDefault.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 95bcb2ab29..6f133bcb67 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -7,6 +7,7 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Logging; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Backgrounds private const int background_count = 7; private IBindable user; private Bindable skin; - private Bindable mode; + private Bindable source; private Bindable introSequence; private readonly SeasonalBackgroundLoader seasonalBackgroundLoader = new SeasonalBackgroundLoader(); @@ -45,14 +46,14 @@ namespace osu.Game.Screens.Backgrounds { user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); - mode = config.GetBindable(OsuSetting.MenuBackgroundSource); + source = config.GetBindable(OsuSetting.MenuBackgroundSource); introSequence = config.GetBindable(OsuSetting.IntroSequence); AddInternal(seasonalBackgroundLoader); user.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); skin.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - mode.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + source.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); beatmap.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); introSequence.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(loadNextIfRequired); @@ -62,7 +63,13 @@ namespace osu.Game.Screens.Backgrounds Next(); // helper function required for AddOnce usage. - void loadNextIfRequired() => Next(); + void loadNextIfRequired() + { + if (!IsLoaded) + return; + + Next(); + } } private ScheduledDelegate nextTask; @@ -80,6 +87,8 @@ namespace osu.Game.Screens.Backgrounds if (nextBackground == background) return false; + Logger.Log("🌅 Background change queued"); + cancellationTokenSource?.Cancel(); cancellationTokenSource = new CancellationTokenSource(); @@ -108,12 +117,12 @@ namespace osu.Game.Screens.Backgrounds if (newBackground == null && user.Value?.IsSupporter == true) { - switch (mode.Value) + switch (source.Value) { case BackgroundSource.Beatmap: case BackgroundSource.BeatmapWithStoryboard: { - if (mode.Value == BackgroundSource.BeatmapWithStoryboard && AllowStoryboardBackground) + if (source.Value == BackgroundSource.BeatmapWithStoryboard && AllowStoryboardBackground) newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, getBackgroundTextureName()); newBackground ??= new BeatmapBackground(beatmap.Value, getBackgroundTextureName()); From 216150b52dab2cdf04df29fbf087221e144c64c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 02:00:02 +0900 Subject: [PATCH 356/363] Avoid always loading new background at `MainMenu` This was meant to be an optimisation to allow the background to load while the intro is playing, but as the current default intro loads a background itself, this was rarely the case and also counter-productive as it would bypass the equality check and start a second load sequence. --- osu.Game/Screens/Menu/MainMenu.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index ba63902b46..066a37055c 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -65,9 +65,7 @@ namespace osu.Game.Screens.Menu [Resolved(canBeNull: true)] private IDialogOverlay dialogOverlay { get; set; } - private BackgroundScreenDefault background; - - protected override BackgroundScreen CreateBackground() => background; + protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); protected override bool PlayExitSound => false; @@ -148,7 +146,6 @@ namespace osu.Game.Screens.Menu Buttons.OnSettings = () => settings?.ToggleVisibility(); Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility(); - LoadComponentAsync(background = new BackgroundScreenDefault()); preloadSongSelect(); } From 15d070668d6401a166abcac63750268c0ea4a8e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 02:01:51 +0900 Subject: [PATCH 357/363] Move intro screen background to base implementation and use colour fading --- osu.Game/Screens/Menu/IntroScreen.cs | 20 ++++++++++++++-- osu.Game/Screens/Menu/IntroTriangles.cs | 32 ++++++------------------- osu.Game/Screens/Menu/IntroWelcome.cs | 19 ++------------- 3 files changed, 27 insertions(+), 44 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index c81195bbd3..e4cf26f2ce 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -88,6 +88,11 @@ namespace osu.Game.Screens.Menu /// protected bool UsingThemedIntro { get; private set; } + protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(false) + { + Colour = Color4.Black + }; + protected IntroScreen([CanBeNull] Func createNextScreen = null) { this.createNextScreen = createNextScreen; @@ -201,6 +206,8 @@ namespace osu.Game.Screens.Menu { this.FadeIn(300); + ApplyToBackground(b => b.FadeColour(Color4.Black)); + double fadeOutTime = exit_delay; var track = musicController.CurrentTrack; @@ -243,13 +250,22 @@ namespace osu.Game.Screens.Menu base.OnResuming(e); } + private bool backgroundFaded; + + protected void FadeInBackground(float fadeInTime) + { + backgroundFaded = true; + ApplyToBackground(b => b.FadeColour(Color4.White, fadeInTime)); + } + public override void OnSuspending(ScreenTransitionEvent e) { base.OnSuspending(e); initialBeatmap = null; - } - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); + if (!backgroundFaded) + FadeInBackground(200); + } protected virtual void StartTrack() { diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index ad098ae8df..7a5d970ef6 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -8,19 +8,18 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; -using osu.Framework.Utils; +using osu.Framework.Screens; using osu.Framework.Timing; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; -using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; @@ -32,16 +31,9 @@ namespace osu.Game.Screens.Menu protected override string BeatmapFile => "triangles.osz"; - protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false) - { - Alpha = 0, - }; - [Resolved] private AudioManager audio { get; set; } - private BackgroundScreenDefault background; - private Sample welcome; private DecoupleableInterpolatingFramedClock decoupledClock; @@ -75,7 +67,7 @@ namespace osu.Game.Screens.Menu if (UsingThemedIntro) decoupledClock.ChangeSource(Track); - LoadComponentAsync(intro = new TrianglesIntroSequence(logo, background) + LoadComponentAsync(intro = new TrianglesIntroSequence(logo, () => FadeInBackground(0)) { RelativeSizeAxes = Axes.Both, Clock = decoupledClock, @@ -95,19 +87,10 @@ namespace osu.Game.Screens.Menu { base.OnSuspending(e); - // ensure the background is shown, even if the TriangleIntroSequence failed to do so. - background.ApplyToBackground(b => b.Show()); - // important as there is a clock attached to a track which will likely be disposed before returning to this screen. intro.Expire(); } - public override void OnResuming(ScreenTransitionEvent e) - { - base.OnResuming(e); - background.FadeOut(100); - } - protected override void StartTrack() { decoupledClock.Start(); @@ -116,7 +99,7 @@ namespace osu.Game.Screens.Menu private class TrianglesIntroSequence : CompositeDrawable { private readonly OsuLogo logo; - private readonly BackgroundScreenDefault background; + private readonly Action showBackgroundAction; private OsuSpriteText welcomeText; private RulesetFlow rulesets; @@ -128,10 +111,10 @@ namespace osu.Game.Screens.Menu public Action LoadMenu; - public TrianglesIntroSequence(OsuLogo logo, BackgroundScreenDefault background) + public TrianglesIntroSequence(OsuLogo logo, Action showBackgroundAction) { this.logo = logo; - this.background = background; + this.showBackgroundAction = showBackgroundAction; } [Resolved] @@ -205,7 +188,6 @@ namespace osu.Game.Screens.Menu rulesets.Hide(); lazerLogo.Hide(); - background.ApplyToBackground(b => b.Hide()); using (BeginAbsoluteSequence(0)) { @@ -267,7 +249,7 @@ namespace osu.Game.Screens.Menu logo.FadeIn(); - background.ApplyToBackground(b => b.Show()); + showBackgroundAction(); game.Add(new GameWideFlash()); diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 031c8d7902..9e56a3a0b7 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -5,11 +5,9 @@ using System; using JetBrains.Annotations; -using osuTK; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -17,8 +15,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Online.API; -using osu.Game.Screens.Backgrounds; using osu.Game.Skinning; +using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Menu @@ -35,13 +33,6 @@ namespace osu.Game.Screens.Menu private ISample pianoReverb; protected override string SeeyaSampleName => "Intro/Welcome/seeya"; - protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false) - { - Alpha = 0, - }; - - private BackgroundScreenDefault background; - public IntroWelcome([CanBeNull] Func createNextScreen = null) : base(createNextScreen) { @@ -100,7 +91,7 @@ namespace osu.Game.Screens.Menu logo.ScaleTo(1); logo.FadeIn(fade_in_time); - background.FadeIn(fade_in_time); + FadeInBackground(fade_in_time); LoadMenu(); }, delay_step_two); @@ -108,12 +99,6 @@ namespace osu.Game.Screens.Menu } } - public override void OnResuming(ScreenTransitionEvent e) - { - base.OnResuming(e); - background.FadeOut(100); - } - private class WelcomeIntroSequence : Container { private Drawable welcomeText; From c53dd4a70315775e603f6806322ffa7e9346c07e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 02:31:33 +0900 Subject: [PATCH 358/363] Fix editor saving not updating `BeatmapSetInfo`'s hash --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 2 ++ osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ osu.Game/Database/RealmArchiveModelImporter.cs | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index bcf02cd814..8e325838ff 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -40,6 +40,8 @@ namespace osu.Game.Tests.Visual.Editing SaveEditor(); + AddAssert("Hash updated", () => !string.IsNullOrEmpty(EditorBeatmap.BeatmapInfo.BeatmapSet?.Hash)); + AddAssert("Beatmap has correct metadata", () => EditorBeatmap.BeatmapInfo.Metadata.Artist == "artist" && EditorBeatmap.BeatmapInfo.Metadata.Title == "title"); AddAssert("Beatmap has correct author", () => EditorBeatmap.BeatmapInfo.Metadata.Author.Username == "author"); AddAssert("Beatmap has correct difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "difficulty"); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b1acf78ec6..30456afd2f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -316,6 +316,8 @@ namespace osu.Game.Beatmaps AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo)); + setInfo.Hash = beatmapImporter.ComputeHash(setInfo); + Realm.Write(r => { var liveBeatmapSet = r.Find(setInfo.ID); diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 76f6db1384..27cd565d90 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -386,7 +386,7 @@ namespace osu.Game.Database /// /// In the case of no matching files, a hash will be generated from the passed archive's . /// - protected string ComputeHash(TModel item) + public string ComputeHash(TModel item) { // for now, concatenate all hashable files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); From 07a08d28c635110b9cc25c1c4ed253c3dfa49e10 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Jul 2022 23:31:04 +0300 Subject: [PATCH 359/363] Rename parameter and default to 0 --- osu.Game/Screens/Menu/IntroScreen.cs | 4 ++-- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index e4cf26f2ce..25d577afb5 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -252,10 +252,10 @@ namespace osu.Game.Screens.Menu private bool backgroundFaded; - protected void FadeInBackground(float fadeInTime) + protected void FadeInBackground(float duration = 0) { + ApplyToBackground(b => b.FadeColour(Color4.White, duration)); backgroundFaded = true; - ApplyToBackground(b => b.FadeColour(Color4.White, fadeInTime)); } public override void OnSuspending(ScreenTransitionEvent e) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 7a5d970ef6..3cdf51a87c 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Menu if (UsingThemedIntro) decoupledClock.ChangeSource(Track); - LoadComponentAsync(intro = new TrianglesIntroSequence(logo, () => FadeInBackground(0)) + LoadComponentAsync(intro = new TrianglesIntroSequence(logo, () => FadeInBackground()) { RelativeSizeAxes = Axes.Both, Clock = decoupledClock, From 1e159eb328ffa3b1aac1dba9217d5c722e344052 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 10:43:50 +0900 Subject: [PATCH 360/363] Add back fade to black duration Co-authored-by: Salman Ahmed --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 25d577afb5..c1621ce78f 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -206,7 +206,7 @@ namespace osu.Game.Screens.Menu { this.FadeIn(300); - ApplyToBackground(b => b.FadeColour(Color4.Black)); + ApplyToBackground(b => b.FadeColour(Color4.Black, 100)); double fadeOutTime = exit_delay; From 26d88aa3263c677c28e92844a8a5d4d8b40ba043 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 8 Jul 2022 14:29:15 +0900 Subject: [PATCH 361/363] Fix intermittent MusicController tests --- osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 14b2593fa7..720e32a242 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -24,10 +24,11 @@ namespace osu.Game.Tests.Visual.Menus public void TestMusicPlayAction() { AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething()); + AddUntilStep("music playing", () => Game.MusicController.IsPlaying); AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); - AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.UserPauseRequested); + AddUntilStep("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.UserPauseRequested); AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay)); - AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.UserPauseRequested); + AddUntilStep("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.UserPauseRequested); } [Test] From 32c77ddf717842f33a143310ce951d9ffbd014da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 15:07:30 +0900 Subject: [PATCH 362/363] Avoid triggering `SeasonalBackgroundChanged` unless actually required --- .../Backgrounds/SeasonalBackgroundLoader.cs | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index bfea3a722c..99af95b5fe 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -38,19 +38,21 @@ namespace osu.Game.Graphics.Backgrounds private void load(OsuConfigManager config, SessionStatics sessionStatics) { seasonalBackgroundMode = config.GetBindable(OsuSetting.SeasonalBackgroundMode); - seasonalBackgroundMode.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke()); + seasonalBackgroundMode.BindValueChanged(_ => triggerSeasonalBackgroundChanged()); seasonalBackgrounds = sessionStatics.GetBindable(Static.SeasonalBackgrounds); - seasonalBackgrounds.BindValueChanged(response => - { - if (response.NewValue?.Backgrounds?.Count > 0) - SeasonalBackgroundChanged?.Invoke(); - }); + seasonalBackgrounds.BindValueChanged(_ => triggerSeasonalBackgroundChanged()); apiState.BindTo(api.State); apiState.BindValueChanged(fetchSeasonalBackgrounds, true); } + private void triggerSeasonalBackgroundChanged() + { + if (shouldShowSeasonal) + SeasonalBackgroundChanged?.Invoke(); + } + private void fetchSeasonalBackgrounds(ValueChangedEvent stateChanged) { if (seasonalBackgrounds.Value != null || stateChanged.NewValue != APIState.Online) @@ -68,15 +70,10 @@ namespace osu.Game.Graphics.Backgrounds public SeasonalBackground LoadNextBackground() { - if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Never - || (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && !isInSeason)) - { + if (!shouldShowSeasonal) return null; - } - var backgrounds = seasonalBackgrounds.Value?.Backgrounds; - if (backgrounds == null || !backgrounds.Any()) - return null; + var backgrounds = seasonalBackgrounds.Value.Backgrounds; current = (current + 1) % backgrounds.Count; string url = backgrounds[current].Url; @@ -84,6 +81,20 @@ namespace osu.Game.Graphics.Backgrounds return new SeasonalBackground(url); } + private bool shouldShowSeasonal + { + get + { + if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Never) + return false; + + if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && !isInSeason) + return false; + + return seasonalBackgrounds.Value?.Backgrounds?.Any() == true; + } + } + private bool isInSeason => seasonalBackgrounds.Value != null && DateTimeOffset.Now < seasonalBackgrounds.Value.EndDate; } From eab3eba70e9c97c79f8ed67ee16e2734ef2f1cff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 15:09:16 +0900 Subject: [PATCH 363/363] Move event handlers to `LoadComplete` --- .../Backgrounds/BackgroundScreenDefault.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 6f133bcb67..c794c768c6 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -51,25 +51,24 @@ namespace osu.Game.Screens.Backgrounds AddInternal(seasonalBackgroundLoader); - user.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - skin.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - source.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - beatmap.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - introSequence.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); - seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(loadNextIfRequired); - + // Load first background asynchronously as part of BDL load. currentDisplay = RNG.Next(0, background_count); - Next(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + user.ValueChanged += _ => Scheduler.AddOnce(next); + skin.ValueChanged += _ => Scheduler.AddOnce(next); + source.ValueChanged += _ => Scheduler.AddOnce(next); + beatmap.ValueChanged += _ => Scheduler.AddOnce(next); + introSequence.ValueChanged += _ => Scheduler.AddOnce(next); + seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(next); // helper function required for AddOnce usage. - void loadNextIfRequired() - { - if (!IsLoaded) - return; - - Next(); - } + void next() => Next(); } private ScheduledDelegate nextTask;