From 10287e01505880f236667a636968c814f250c137 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 26 May 2022 00:08:00 -0400 Subject: [PATCH 001/139] initial implementation --- .../Mods/OsuEaseHitObjectPositionsMod.cs | 75 +++++++++++++++++++ .../Mods/OsuModMagnetised.cs | 59 ++------------- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 39 ++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 4 files changed, 121 insertions(+), 54 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs new file mode 100644 index 0000000000..d81d86dc65 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -0,0 +1,75 @@ +// 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.Framework.Utils; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public abstract class OsuEaseHitObjectPositionsMod : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset + { + public override ModType Type => ModType.Fun; + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; + + protected BindableFloat EasementStrength = new BindableFloat(0.5f); + protected Vector2 CursorPosition; + protected DrawableHitObject WorkingHitObject; + protected abstract Vector2 DestinationVector { get; } + + private IFrameStableClock gameplayClock; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + gameplayClock = drawableRuleset.FrameStableClock; + + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + } + + public void Update(Playfield playfield) + { + CursorPosition = playfield.Cursor.ActiveCursor.DrawPosition; + + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + { + WorkingHitObject = drawable; + switch (drawable) + { + case DrawableHitCircle circle: + easeHitObjectPositionToVector(circle, DestinationVector); + break; + + case DrawableSlider slider: + + if (!slider.HeadCircle.Result.HasResult) + easeHitObjectPositionToVector(slider, DestinationVector); + else + easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); + + break; + } + } + } + + private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, Math.Clamp(destination.X, 0, OsuPlayfield.BASE_SIZE.X), dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, Math.Clamp(destination.Y, 0, OsuPlayfield.BASE_SIZE.Y), dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index ca6e9cfb1d..ca69085e31 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -2,31 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; -using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModMagnetised : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset + internal class OsuModMagnetised : OsuEaseHitObjectPositionsMod { public override string Name => "Magnetised"; public override string Acronym => "MG"; public override IconUsage? Icon => FontAwesome.Solid.Magnet; - public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circles – your cursor is a magnet!"; - public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax) }; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModRelax), typeof(OsuModRepel) }).ToArray(); - private IFrameStableClock gameplayClock; + protected override Vector2 DestinationVector => CursorPosition; [SettingSource("Attraction strength", "How strong the pull is.", 0)] public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) @@ -36,48 +28,9 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + public OsuModMagnetised() { - gameplayClock = drawableRuleset.FrameStableClock; - - // Hide judgment displays and follow points as they won't make any sense. - // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; - (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); - } - - public void Update(Playfield playfield) - { - var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) - { - switch (drawable) - { - case DrawableHitCircle circle: - easeTo(circle, cursorPos); - break; - - case DrawableSlider slider: - - if (!slider.HeadCircle.Result.HasResult) - easeTo(slider, cursorPos); - else - easeTo(slider, cursorPos - slider.Ball.DrawPosition); - - break; - } - } - } - - private void easeTo(DrawableHitObject hitObject, Vector2 destination) - { - double dampLength = Interpolation.Lerp(3000, 40, AttractionStrength.Value); - - float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); - float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); - - hitObject.Position = new Vector2(x, y); + EasementStrength.BindTo(AttractionStrength); } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs new file mode 100644 index 0000000000..35f369d9c5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModRepel : OsuEaseHitObjectPositionsMod + { + public override string Name => "Repel"; + public override string Acronym => "RP"; + public override IconUsage? Icon => FontAwesome.Solid.ExpandArrowsAlt; + public override string Description => "Run away!"; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); + + [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] + public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) + { + Precision = 0.05f, + MinValue = 0.05f, + MaxValue = 1.0f, + }; + + protected override Vector2 DestinationVector => new Vector2( + 2 * WorkingHitObject.X - CursorPosition.X, + 2 * WorkingHitObject.Y - CursorPosition.Y + ); + + public OsuModRepel() + { + EasementStrength.BindTo(RepulsionStrength); + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 207e7a4ab0..09b85726ca 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu new OsuModApproachDifferent(), new OsuModMuted(), new OsuModNoScope(), - new OsuModMagnetised(), + new MultiMod(new OsuModMagnetised(), new OsuModRepel()), new ModAdaptiveSpeed() }; From 5d838628d7e49df3b3ce3e1768e6c19c68e366fe Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Fri, 27 May 2022 23:15:19 -0400 Subject: [PATCH 002/139] add test, fix formatting, expose easing function --- .../Mods/TestSceneOsuModRepel.cs | 27 +++++++++++++++++++ .../Mods/OsuEaseHitObjectPositionsMod.cs | 12 ++++----- 2 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs new file mode 100644 index 0000000000..6bd41e2fa5 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.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 NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModRepel : OsuModTestScene + { + [TestCase(0.1f)] + [TestCase(0.5f)] + [TestCase(1)] + public void TestRepel(float strength) + { + CreateModTest(new ModTestData + { + Mod = new OsuModRepel + { + RepulsionStrength = { Value = strength }, + }, + PassCondition = () => true, + Autoplay = false, + }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index d81d86dc65..2c344b7a68 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods protected BindableFloat EasementStrength = new BindableFloat(0.5f); protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; - protected abstract Vector2 DestinationVector { get; } - + protected virtual Vector2 DestinationVector => WorkingHitObject.Position; + private IFrameStableClock gameplayClock; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -47,22 +47,22 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - easeHitObjectPositionToVector(circle, DestinationVector); + EaseHitObjectPositionToVector(circle, DestinationVector); break; case DrawableSlider slider: if (!slider.HeadCircle.Result.HasResult) - easeHitObjectPositionToVector(slider, DestinationVector); + EaseHitObjectPositionToVector(slider, DestinationVector); else - easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); + EaseHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); break; } } } - private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) + protected void EaseHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); From de224e79c79863f268367392574df539d6ddac80 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 7 Jun 2022 10:32:51 +0800 Subject: [PATCH 003/139] Limit slider rotation when the slider is too large --- .../OsuHitObjectGenerationUtils_Reposition.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index a77d1f8b0f..477ef2d55d 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -198,6 +198,27 @@ namespace osu.Game.Rulesets.Osu.Utils var slider = (Slider)workingObject.HitObject; var possibleMovementBounds = calculatePossibleMovementBounds(slider); + // The slider rotation applied in computeModifiedPosition might make it impossible to fit the slider into the playfield + // For example, a long horizontal slider will be off-screen when rotated by 90 degrees + // In this case, limit the rotation to either 0 or 180 degrees + if (possibleMovementBounds.Width < 0 || possibleMovementBounds.Height < 0) + { + float currentRotation = getSliderRotation(slider); + float diff1 = getAngleDifference(workingObject.RotationOriginal, currentRotation); + float diff2 = getAngleDifference(workingObject.RotationOriginal + MathF.PI, currentRotation); + + if (diff1 < diff2) + { + RotateSlider(slider, workingObject.RotationOriginal - getSliderRotation(slider)); + } + else + { + RotateSlider(slider, workingObject.RotationOriginal + MathF.PI - getSliderRotation(slider)); + } + + possibleMovementBounds = calculatePossibleMovementBounds(slider); + } + var previousPosition = workingObject.PositionModified; // Clamp slider position to the placement area @@ -355,6 +376,18 @@ namespace osu.Game.Rulesets.Osu.Utils return MathF.Atan2(endPositionVector.Y, endPositionVector.X); } + /// + /// Get the absolute difference between 2 angles measured in Radians. + /// + /// The first angle + /// The second angle + /// The absolute difference with interval [0, MathF.PI) + private static float getAngleDifference(float angle1, float angle2) + { + float diff = MathF.Abs(angle1 - angle2) % (MathF.PI * 2); + return MathF.Min(diff, MathF.PI * 2 - diff); + } + public class ObjectPositionInfo { /// @@ -397,6 +430,7 @@ namespace osu.Game.Rulesets.Osu.Utils private class WorkingObject { + public float RotationOriginal { get; } public Vector2 PositionOriginal { get; } public Vector2 PositionModified { get; set; } public Vector2 EndPositionModified { get; set; } @@ -407,6 +441,7 @@ namespace osu.Game.Rulesets.Osu.Utils public WorkingObject(ObjectPositionInfo positionInfo) { PositionInfo = positionInfo; + RotationOriginal = HitObject is Slider slider ? getSliderRotation(slider) : 0; PositionModified = PositionOriginal = HitObject.Position; EndPositionModified = HitObject.EndPosition; } From b7bdad407486266b5c5446fde71c7108c0adc225 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:36:44 -0400 Subject: [PATCH 004/139] clamp sliders, expose slider bounds function --- .../Mods/OsuEaseHitObjectPositionsMod.cs | 16 ++++----- .../Mods/OsuModMagnetised.cs | 7 +--- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 33 ++++++++++++++----- .../OsuHitObjectGenerationUtils_Reposition.cs | 22 ++++++++----- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index 2c344b7a68..74391e53a2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -20,10 +20,10 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - protected BindableFloat EasementStrength = new BindableFloat(0.5f); + public abstract BindableFloat EasementStrength { get; } protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; - protected virtual Vector2 DestinationVector => WorkingHitObject.Position; + protected abstract Vector2 DestinationVector { get; } private IFrameStableClock gameplayClock; @@ -47,27 +47,27 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - EaseHitObjectPositionToVector(circle, DestinationVector); + easeHitObjectPositionToVector(circle, DestinationVector); break; case DrawableSlider slider: if (!slider.HeadCircle.Result.HasResult) - EaseHitObjectPositionToVector(slider, DestinationVector); + easeHitObjectPositionToVector(slider, DestinationVector); else - EaseHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); + easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); break; } } } - protected void EaseHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) + private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); - float x = (float)Interpolation.DampContinuously(hitObject.X, Math.Clamp(destination.X, 0, OsuPlayfield.BASE_SIZE.X), dampLength, gameplayClock.ElapsedFrameTime); - float y = (float)Interpolation.DampContinuously(hitObject.Y, Math.Clamp(destination.Y, 0, OsuPlayfield.BASE_SIZE.Y), dampLength, gameplayClock.ElapsedFrameTime); + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); hitObject.Position = new Vector2(x, y); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index ca69085e31..1c10d61c99 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -21,16 +21,11 @@ namespace osu.Game.Rulesets.Osu.Mods protected override Vector2 DestinationVector => CursorPosition; [SettingSource("Attraction strength", "How strong the pull is.", 0)] - public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - - public OsuModMagnetised() - { - EasementStrength.BindTo(AttractionStrength); - } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 35f369d9c5..fec66e3ef1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -6,6 +6,9 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Utils; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods @@ -19,21 +22,35 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - protected override Vector2 DestinationVector => new Vector2( - 2 * WorkingHitObject.X - CursorPosition.X, - 2 * WorkingHitObject.Y - CursorPosition.Y - ); - - public OsuModRepel() + protected override Vector2 DestinationVector { - EasementStrength.BindTo(RepulsionStrength); + get + { + float x = Math.Clamp(2 * WorkingHitObject.X - CursorPosition.X, 0, OsuPlayfield.BASE_SIZE.X); + float y = Math.Clamp(2 * WorkingHitObject.Y - CursorPosition.Y, 0, OsuPlayfield.BASE_SIZE.Y); + + if (WorkingHitObject.HitObject is Slider slider) + { + var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider, false); + + x = possibleMovementBounds.Width < 0 + ? x + : Math.Clamp(x, possibleMovementBounds.Left, possibleMovementBounds.Right); + + y = possibleMovementBounds.Height < 0 + ? y + : Math.Clamp(y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); + } + + return new Vector2(x, y); + } } } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index d1bc3b45df..765477fba2 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Utils private static Vector2 clampSliderToPlayfield(WorkingObject workingObject) { var slider = (Slider)workingObject.HitObject; - var possibleMovementBounds = calculatePossibleMovementBounds(slider); + var possibleMovementBounds = CalculatePossibleMovementBounds(slider); var previousPosition = workingObject.PositionModified; @@ -212,10 +212,13 @@ namespace osu.Game.Rulesets.Osu.Utils /// Calculates a which contains all of the possible movements of the slider (in relative X/Y coordinates) /// such that the entire slider is inside the playfield. /// + /// The for which to calculate a movement bounding box. + /// Whether the movement bounding box should account for the slider's follow circle. Defaults to true. + /// A which contains all of the possible movements of the slider such that the entire slider is inside the playfield. /// /// If the slider is larger than the playfield, the returned may have negative width/height. /// - private static RectangleF calculatePossibleMovementBounds(Slider slider) + public static RectangleF CalculatePossibleMovementBounds(Slider slider, bool accountForFollowCircleRadius = true) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); @@ -236,14 +239,17 @@ namespace osu.Game.Rulesets.Osu.Utils maxY = MathF.Max(maxY, pos.Y); } - // Take the circle radius into account. - float radius = (float)slider.Radius; + if (accountForFollowCircleRadius) + { + // Take the circle radius into account. + float radius = (float)slider.Radius; - minX -= radius; - minY -= radius; + minX -= radius; + minY -= radius; - maxX += radius; - maxY += radius; + maxX += radius; + maxY += radius; + } // Given the bounding box of the slider (via min/max X/Y), // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), From e5171aaebf60a7ab17b31f7295a71f4278efde4b Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:48:53 -0400 Subject: [PATCH 005/139] update tests to match new bindable names --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs index 9b49e60363..29ec91cda8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModMagnetised { - AttractionStrength = { Value = strength }, + EasementStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs index 6bd41e2fa5..bdd4ed7fb9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModRepel { - RepulsionStrength = { Value = strength }, + EasementStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, From f21c9fb520190d51a9e499b57f5410ab16978564 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 7 Jun 2022 12:05:53 -0400 Subject: [PATCH 006/139] add newline --- osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index 74391e53a2..d0f115def3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { WorkingHitObject = drawable; + switch (drawable) { case DrawableHitCircle circle: From f54a68f6cae2ff193c6ad94b23d11f918ec3197f Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 8 Jun 2022 01:00:47 -0400 Subject: [PATCH 007/139] scale down repulsion strength --- osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs | 3 ++- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index d0f115def3..e93863fa96 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; public abstract BindableFloat EasementStrength { get; } + protected virtual float EasementStrengthMultiplier => 1.0f; protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; protected abstract Vector2 DestinationVector { get; } @@ -65,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); + double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value * EasementStrengthMultiplier); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index fec66e3ef1..95242808af 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -22,13 +22,15 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasementStrength { get; } = new BindableFloat(0.6f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; + protected override float EasementStrengthMultiplier => 0.8f; + protected override Vector2 DestinationVector { get From 6e883a69d9e98947053646d6f166afe0bf4a277a Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:07:37 -0400 Subject: [PATCH 008/139] revert slider radius parameter addition --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- .../OsuHitObjectGenerationUtils_Reposition.cs | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 95242808af..3261076084 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (WorkingHitObject.HitObject is Slider slider) { - var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider, false); + var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider); x = possibleMovementBounds.Width < 0 ? x diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 765477fba2..5e11ede91f 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -213,12 +213,11 @@ namespace osu.Game.Rulesets.Osu.Utils /// such that the entire slider is inside the playfield. /// /// The for which to calculate a movement bounding box. - /// Whether the movement bounding box should account for the slider's follow circle. Defaults to true. /// A which contains all of the possible movements of the slider such that the entire slider is inside the playfield. /// /// If the slider is larger than the playfield, the returned may have negative width/height. /// - public static RectangleF CalculatePossibleMovementBounds(Slider slider, bool accountForFollowCircleRadius = true) + public static RectangleF CalculatePossibleMovementBounds(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); @@ -239,17 +238,14 @@ namespace osu.Game.Rulesets.Osu.Utils maxY = MathF.Max(maxY, pos.Y); } - if (accountForFollowCircleRadius) - { - // Take the circle radius into account. - float radius = (float)slider.Radius; + // Take the circle radius into account. + float radius = (float)slider.Radius; - minX -= radius; - minY -= radius; + minX -= radius; + minY -= radius; - maxX += radius; - maxY += radius; - } + maxX += radius; + maxY += radius; // Given the bounding box of the slider (via min/max X/Y), // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), From 4e01db03bb3a6002590dac58e04005cf9c528193 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:25:04 -0400 Subject: [PATCH 009/139] don't specify icon --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 3261076084..da6686d314 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Utils; using osu.Game.Rulesets.Osu.Objects; @@ -17,7 +16,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Repel"; public override string Acronym => "RP"; - public override IconUsage? Icon => FontAwesome.Solid.ExpandArrowsAlt; public override string Description => "Run away!"; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); From 569c39942a5b74ae51847c64e0f230f8973ec994 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:26:18 -0400 Subject: [PATCH 010/139] replace easement with easing --- .../Mods/TestSceneOsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs | 6 +++--- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs index 29ec91cda8..bab47fa851 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModMagnetised { - EasementStrength = { Value = strength }, + EasingStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs index bdd4ed7fb9..0462f60991 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModRepel { - EasementStrength = { Value = strength }, + EasingStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index e93863fa96..0ba3a204f9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - public abstract BindableFloat EasementStrength { get; } - protected virtual float EasementStrengthMultiplier => 1.0f; + public abstract BindableFloat EasingStrength { get; } + protected virtual float EasingStrengthMultiplier => 1.0f; protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; protected abstract Vector2 DestinationVector { get; } @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value * EasementStrengthMultiplier); + double dampLength = Interpolation.Lerp(3000, 40, EasingStrength.Value * EasingStrengthMultiplier); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index 1c10d61c99..b8c976cf9f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected override Vector2 DestinationVector => CursorPosition; [SettingSource("Attraction strength", "How strong the pull is.", 0)] - public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasingStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index da6686d314..1622400eac 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -20,14 +20,14 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public override BindableFloat EasementStrength { get; } = new BindableFloat(0.6f) + public override BindableFloat EasingStrength { get; } = new BindableFloat(0.6f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - protected override float EasementStrengthMultiplier => 0.8f; + protected override float EasingStrengthMultiplier => 0.8f; protected override Vector2 DestinationVector { From 2fe34f188f4c6d4294cd98f2f4d18a921f5949a1 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:52:10 -0400 Subject: [PATCH 011/139] shamelessly copy osumodmagnetised --- .../Mods/TestSceneOsuModMagnetised.cs | 2 +- .../Mods/TestSceneOsuModRepel.cs | 2 +- .../Mods/OsuEaseHitObjectPositionsMod.cs | 77 ------------------- .../Mods/OsuModMagnetised.cs | 62 +++++++++++++-- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 74 ++++++++++++++---- 5 files changed, 118 insertions(+), 99 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs index bab47fa851..9b49e60363 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModMagnetised { - EasingStrength = { Value = strength }, + AttractionStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs index 0462f60991..6bd41e2fa5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModRepel { - EasingStrength = { Value = strength }, + RepulsionStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs deleted file mode 100644 index 0ba3a204f9..0000000000 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ /dev/null @@ -1,77 +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 osu.Framework.Bindables; -using osu.Framework.Utils; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public abstract class OsuEaseHitObjectPositionsMod : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset - { - public override ModType Type => ModType.Fun; - public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - - public abstract BindableFloat EasingStrength { get; } - protected virtual float EasingStrengthMultiplier => 1.0f; - protected Vector2 CursorPosition; - protected DrawableHitObject WorkingHitObject; - protected abstract Vector2 DestinationVector { get; } - - private IFrameStableClock gameplayClock; - - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - gameplayClock = drawableRuleset.FrameStableClock; - - // Hide judgment displays and follow points as they won't make any sense. - // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; - (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); - } - - public void Update(Playfield playfield) - { - CursorPosition = playfield.Cursor.ActiveCursor.DrawPosition; - - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) - { - WorkingHitObject = drawable; - - switch (drawable) - { - case DrawableHitCircle circle: - easeHitObjectPositionToVector(circle, DestinationVector); - break; - - case DrawableSlider slider: - - if (!slider.HeadCircle.Result.HasResult) - easeHitObjectPositionToVector(slider, DestinationVector); - else - easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); - - break; - } - } - } - - private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) - { - double dampLength = Interpolation.Lerp(3000, 40, EasingStrength.Value * EasingStrengthMultiplier); - - float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); - float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); - - hitObject.Position = new Vector2(x, y); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index b8c976cf9f..97a573f1b4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -2,30 +2,82 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModMagnetised : OsuEaseHitObjectPositionsMod + internal class OsuModMagnetised : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Magnetised"; public override string Acronym => "MG"; public override IconUsage? Icon => FontAwesome.Solid.Magnet; + public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circles – your cursor is a magnet!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModRelax), typeof(OsuModRepel) }).ToArray(); + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) }; - protected override Vector2 DestinationVector => CursorPosition; + private IFrameStableClock gameplayClock; [SettingSource("Attraction strength", "How strong the pull is.", 0)] - public override BindableFloat EasingStrength { get; } = new BindableFloat(0.5f) + public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + gameplayClock = drawableRuleset.FrameStableClock; + + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + } + + public void Update(Playfield playfield) + { + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + { + switch (drawable) + { + case DrawableHitCircle circle: + easeTo(circle, cursorPos); + break; + + case DrawableSlider slider: + + if (!slider.HeadCircle.Result.HasResult) + easeTo(slider, cursorPos); + else + easeTo(slider, cursorPos - slider.Ball.DrawPosition); + + break; + } + } + } + + private void easeTo(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(3000, 40, AttractionStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); + } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 1622400eac..807be997db 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -2,43 +2,61 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Rulesets.Osu.Utils; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModRepel : OsuEaseHitObjectPositionsMod + internal class OsuModRepel : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Repel"; public override string Acronym => "RP"; + public override ModType Type => ModType.Fun; public override string Description => "Run away!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) }; + + private IFrameStableClock gameplayClock; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public override BindableFloat EasingStrength { get; } = new BindableFloat(0.6f) + public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.6f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - protected override float EasingStrengthMultiplier => 0.8f; - - protected override Vector2 DestinationVector + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - get - { - float x = Math.Clamp(2 * WorkingHitObject.X - CursorPosition.X, 0, OsuPlayfield.BASE_SIZE.X); - float y = Math.Clamp(2 * WorkingHitObject.Y - CursorPosition.Y, 0, OsuPlayfield.BASE_SIZE.Y); + gameplayClock = drawableRuleset.FrameStableClock; - if (WorkingHitObject.HitObject is Slider slider) + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + } + + public void Update(Playfield playfield) + { + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + { + float x = Math.Clamp(2 * drawable.X - cursorPos.X, 0, OsuPlayfield.BASE_SIZE.X); + float y = Math.Clamp(2 * drawable.Y - cursorPos.Y, 0, OsuPlayfield.BASE_SIZE.Y); + + if (drawable.HitObject is Slider thisSlider) { - var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider); + var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(thisSlider); x = possibleMovementBounds.Width < 0 ? x @@ -49,8 +67,34 @@ namespace osu.Game.Rulesets.Osu.Mods : Math.Clamp(y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); } - return new Vector2(x, y); + var destination = new Vector2(x, y); + + switch (drawable) + { + case DrawableHitCircle circle: + easeTo(circle, destination); + break; + + case DrawableSlider slider: + + if (!slider.HeadCircle.Result.HasResult) + easeTo(slider, destination); + else + easeTo(slider, destination - slider.Ball.DrawPosition); + + break; + } } } + + private void easeTo(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(3000, 40, 0.8 * RepulsionStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); + } } } From a17e18103f983649af80c9a15e1f14b3e6e52774 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 18:19:31 +0900 Subject: [PATCH 012/139] Improve description --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 807be997db..052c7128f6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => "Repel"; public override string Acronym => "RP"; public override ModType Type => ModType.Fun; - public override string Description => "Run away!"; + public override string Description => "Hit objects run away!"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) }; From 59e3d4b9e571cf46abd21e520ba316d4c3bff056 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 21:00:54 +0800 Subject: [PATCH 013/139] 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 014/139] 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 015/139] 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 df152421a8e5b75ab2af2d522343828bce780328 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 3 Jul 2022 10:23:17 -0700 Subject: [PATCH 016/139] 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 017/139] 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 018/139] 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 019/139] 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 020/139] 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 021/139] 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 022/139] 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 023/139] 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 024/139] 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 025/139] 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 026/139] 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 027/139] 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 028/139] 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 029/139] 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 030/139] 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 031/139] 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 032/139] 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 033/139] 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 034/139] 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 035/139] 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 036/139] 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 037/139] 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 038/139] 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 039/139] 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 040/139] 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 0281bf672c42d5cedf8a7ac607dec280ad4acbdd Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 15:58:25 -0400 Subject: [PATCH 041/139] operate on vectors instead of vector components --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 052c7128f6..ad3a54c630 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -51,24 +51,19 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { - float x = Math.Clamp(2 * drawable.X - cursorPos.X, 0, OsuPlayfield.BASE_SIZE.X); - float y = Math.Clamp(2 * drawable.Y - cursorPos.Y, 0, OsuPlayfield.BASE_SIZE.Y); + var destination = Vector2.Clamp(2 * drawable.Position - cursorPos, Vector2.Zero, OsuPlayfield.BASE_SIZE); if (drawable.HitObject is Slider thisSlider) { var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(thisSlider); - x = possibleMovementBounds.Width < 0 - ? x - : Math.Clamp(x, possibleMovementBounds.Left, possibleMovementBounds.Right); - - y = possibleMovementBounds.Height < 0 - ? y - : Math.Clamp(y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); + destination = Vector2.Clamp( + destination, + new Vector2(possibleMovementBounds.Left, possibleMovementBounds.Top), + new Vector2(possibleMovementBounds.Right, possibleMovementBounds.Bottom) + ); } - var destination = new Vector2(x, y); - switch (drawable) { case DrawableHitCircle circle: From 40e98f84f304c6288c2decbecda614f5d8b1042d Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:01:08 -0400 Subject: [PATCH 042/139] change default strength back to 0.5 --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index ad3a54c630..c53ac58752 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods private IFrameStableClock gameplayClock; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.6f) + public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, From 7f94405c9e4ea5bc81416b270cb9cce22a1cc4b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Jul 2022 23:38:11 +0300 Subject: [PATCH 043/139] 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 d5b4d146708a5d712924d02a479f8063e152ab5a Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:01:14 -0400 Subject: [PATCH 044/139] modify damp length to effectively invert repulsion strength --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index c53ac58752..0aab019d73 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeTo(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(3000, 40, 0.8 * RepulsionStrength.Value); + double dampLength = Vector2.Distance(hitObject.Position, destination) / (0.04 * RepulsionStrength.Value + 0.04); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); From 62beae4063846c0022930925506d48435985d756 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:18:21 -0400 Subject: [PATCH 045/139] add nullable directive --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 0aab019d73..1cb5bf31a4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -1,6 +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; using osu.Framework.Bindables; using osu.Framework.Utils; From a5b01b89201ef4ba5db1fa64f370d180aa80d567 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Jul 2022 01:00:42 +0300 Subject: [PATCH 046/139] 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 047/139] 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 048/139] 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 049/139] 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 050/139] 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 9d730f84400b236e0cabd8a8f01dc4fd6e78d09c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 14:49:22 +0900 Subject: [PATCH 051/139] Fix custom rulesets not importing scores at all Replaces the error with the ability to import, minus replays. Closes https://github.com/ppy/osu/issues/17350 (arguably, but let's go with it for now). --- osu.Game/Screens/Play/Player.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4040adc48d..b4016fc1cf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -22,6 +22,7 @@ using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; using osu.Game.Online.API; @@ -1064,12 +1065,15 @@ namespace osu.Game.Screens.Play if (DrawableRuleset.ReplayScore != null) return Task.CompletedTask; - LegacyByteArrayReader replayReader; + LegacyByteArrayReader replayReader = null; - using (var stream = new MemoryStream()) + if (score.ScoreInfo.Ruleset.IsLegacyRuleset()) { - new LegacyScoreEncoder(score, GameplayState.Beatmap).Encode(stream); - replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); + using (var stream = new MemoryStream()) + { + new LegacyScoreEncoder(score, GameplayState.Beatmap).Encode(stream); + replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); + } } // the import process will re-attach managed beatmap/rulesets to this score. we don't want this for now, so create a temporary copy to import. From f88e2aa025e676ad2f3d82c459fd3b25420f1a24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 15:02:03 +0900 Subject: [PATCH 052/139] Remove EF test workaround --- .../Gameplay/TestScenePlayerScoreSubmission.cs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index e0c8989389..501ad9bd28 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -363,23 +363,9 @@ namespace osu.Game.Tests.Visual.Gameplay await AllowImportCompletion.WaitAsync().ConfigureAwait(false); - ImportedScore = score; + await base.ImportScore(score); - // It was discovered that Score members could sometimes be half-populated. - // In particular, the RulesetID property could be set to 0 even on non-osu! maps. - // We want to test that the state of that property is consistent in this test. - // EF makes this impossible. - // - // First off, because of the EF navigational property-explicit foreign key field duality, - // it can happen that - for example - the Ruleset navigational property is correctly initialised to mania, - // but the RulesetID foreign key property is not initialised and remains 0. - // EF silently bypasses this by prioritising the Ruleset navigational property over the RulesetID foreign key one. - // - // Additionally, adding an entity to an EF DbSet CAUSES SIDE EFFECTS with regard to the foreign key property. - // In the above instance, if a ScoreInfo with Ruleset = {mania} and RulesetID = 0 is attached to an EF context, - // RulesetID WILL BE SILENTLY SET TO THE CORRECT VALUE of 3. - // - // For the above reasons, actual importing is disabled in this test. + ImportedScore = score; } } } From 1a41d3ef209302df0976302c15f96b5604272615 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 16:42:36 +0900 Subject: [PATCH 053/139] Allow `PlayerTestScene` to import the beatmap it's using --- osu.Game/Tests/Visual/PlayerTestScene.cs | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index e1714299b6..036ac8a639 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -4,12 +4,15 @@ #nullable disable using System; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -22,6 +25,11 @@ namespace osu.Game.Tests.Visual /// protected virtual bool HasCustomSteps => false; + /// + /// WARNING: ONLY WORKS IF RUN HEADLESS because reasons. + /// + protected virtual bool ImportBeatmapToDatabase => false; + protected TestPlayer Player; protected OsuConfigManager LocalConfig; @@ -49,7 +57,7 @@ namespace osu.Game.Tests.Visual action?.Invoke(); - AddStep(CreatePlayerRuleset().Description, LoadPlayer); + AddStep($"Load player for {CreatePlayerRuleset().Description}", LoadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); } @@ -57,6 +65,9 @@ namespace osu.Game.Tests.Visual protected virtual bool Autoplay => false; + [Resolved] + private BeatmapManager beatmaps { get; set; } + protected void LoadPlayer() { var ruleset = CreatePlayerRuleset(); @@ -64,6 +75,20 @@ namespace osu.Game.Tests.Visual var beatmap = CreateBeatmap(ruleset.RulesetInfo); + if (ImportBeatmapToDatabase) + { + Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); + + var imported = beatmaps.Import(beatmap.BeatmapInfo.BeatmapSet); + + Debug.Assert(imported != null); + + beatmap.BeatmapInfo = null; + beatmap.Difficulty = null; + + beatmap.BeatmapInfo = imported.Value.Detach().Beatmaps.First(); + } + Beatmap.Value = CreateWorkingBeatmap(beatmap); SelectedMods.Value = Array.Empty(); From 461d133c1f0a9a736bfa265e5c7dd060d6dd68b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 16:42:48 +0900 Subject: [PATCH 054/139] Add test coverage of score importing --- .../TestScenePlayerLocalScoreImport.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs new file mode 100644 index 0000000000..61f0483c86 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -0,0 +1,72 @@ +// 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.Screens; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [HeadlessTest] // Importing rulesets doesn't work in interactive flows. + public class TestScenePlayerLocalScoreImport : PlayerTestScene + { + private Ruleset? customRuleset; + + protected override bool ImportBeatmapToDatabase => true; + + protected override Ruleset CreatePlayerRuleset() => customRuleset ?? new OsuRuleset(); + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); + + protected override bool HasCustomSteps => true; + + protected override bool AllowFail => false; + + [Test] + public void TestScoreStoredLocally() + { + AddStep("set no custom ruleset", () => customRuleset = null); + + CreateTest(); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); + } + + [Test] + public void TestScoreStoredLocallyCustomRuleset() + { + Ruleset createCustomRuleset() => new OsuRuleset + { + RulesetInfo = + { + Name = "custom", + ShortName = "custom", + OnlineID = -1 + } + }; + + AddStep("import custom ruleset", () => Realm.Write(r => r.Add(createCustomRuleset().RulesetInfo))); + AddStep("set custom ruleset", () => customRuleset = createCustomRuleset()); + + CreateTest(); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + AddUntilStep("score in database", () => Realm.Run(r => r.All().Count() == 1)); + } + } +} From b663986b9fd2922ca3a29e4de95af012c04f30a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Apr 2022 16:12:06 +0900 Subject: [PATCH 055/139] Add test coverage of locally available without replay button handling --- .../Gameplay/TestSceneReplayDownloadButton.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 10a6b196b0..c259d5f0a8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -142,6 +142,28 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } + [Test] + public void TestLocallyAvailableWithoutReplay() + { + Live imported = null; + + AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(false, false))); + + AddStep("create button without replay", () => + { + Child = downloadButton = new TestReplayDownloadButton(imported.Value) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + AddUntilStep("wait for load", () => downloadButton.IsLoaded); + + AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); + } + [Test] public void TestScoreImportThenDelete() { @@ -189,11 +211,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } - private ScoreInfo getScoreInfo(bool replayAvailable) + private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) { return new APIScore { - OnlineID = online_score_id, + OnlineID = hasOnlineId ? online_score_id : 0, RulesetID = 0, Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(), HasReplay = replayAvailable, From 45c5b7e7dd4061157195b9f4f5a1a5fb388467f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:13:16 +0900 Subject: [PATCH 056/139] 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 057/139] 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 058/139] 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 059/139] 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 57b2f8189c4bdd3dd91bf00b035c3810ec44cb19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:40:47 +0900 Subject: [PATCH 060/139] Add back test workaround for `TestScenePlayerScoreSubmission` with updated explanation This reverts commit f88e2aa025e676ad2f3d82c459fd3b25420f1a24. --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 501ad9bd28..96efca6b65 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -363,9 +363,11 @@ namespace osu.Game.Tests.Visual.Gameplay await AllowImportCompletion.WaitAsync().ConfigureAwait(false); - await base.ImportScore(score); - ImportedScore = score; + + // Calling base.ImportScore is omitted as it will fail for the test method which uses a custom ruleset. + // This can be resolved by doing something similar to what TestScenePlayerLocalScoreImport is doing, + // but requires a bit of restructuring. } } } From dd5b127fb5e3e742053ff17b3965b4ecabd1bcc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:51:49 +0900 Subject: [PATCH 061/139] 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 062/139] 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 063/139] 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 064/139] 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 065/139] 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 066/139] 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 067/139] 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 068/139] 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 069/139] 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 070/139] 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 071/139] 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 072/139] 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 073/139] 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 074/139] 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 075/139] 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 89f1c75f7ad0cdea5e23240eeb4580c1cc0a066b Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 7 Jul 2022 21:57:18 -0500 Subject: [PATCH 076/139] Update mod icon colors --- osu.Game/Rulesets/UI/ModIcon.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 0f21a497b0..f32c298571 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -127,33 +127,33 @@ namespace osu.Game.Rulesets.UI { default: case ModType.DifficultyIncrease: - backgroundColour = colours.Yellow; - highlightedColour = colours.YellowLight; + backgroundColour = colours.Red1; + highlightedColour = colours.Red0; break; case ModType.DifficultyReduction: - backgroundColour = colours.Green; - highlightedColour = colours.GreenLight; + backgroundColour = colours.Lime1; + highlightedColour = colours.Lime0; break; case ModType.Automation: - backgroundColour = colours.Blue; - highlightedColour = colours.BlueLight; + backgroundColour = colours.Blue1; + highlightedColour = colours.Blue0; break; case ModType.Conversion: - backgroundColour = colours.Purple; - highlightedColour = colours.PurpleLight; + backgroundColour = colours.Purple1; + highlightedColour = colours.Purple0; break; case ModType.Fun: - backgroundColour = colours.Pink; - highlightedColour = colours.PinkLight; + backgroundColour = colours.Pink1; + highlightedColour = colours.Pink0; break; case ModType.System: - backgroundColour = colours.Gray6; - highlightedColour = colours.Gray7; + backgroundColour = colours.Gray7; + highlightedColour = colours.Gray8; modIcon.Colour = colours.Yellow; break; } From 26d88aa3263c677c28e92844a8a5d4d8b40ba043 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 8 Jul 2022 14:29:15 +0900 Subject: [PATCH 077/139] 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 078/139] 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 079/139] 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; From e64b2b1682602020a3bd68f3af3f45c44f95d832 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 15:45:01 +0900 Subject: [PATCH 080/139] Assert that test is run headless when required --- osu.Game/Tests/Visual/PlayerTestScene.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 036ac8a639..5b4d4d5c2c 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -26,8 +27,12 @@ namespace osu.Game.Tests.Visual protected virtual bool HasCustomSteps => false; /// - /// WARNING: ONLY WORKS IF RUN HEADLESS because reasons. + /// Import the beatmap to the database before starting gameplay. Handy for testing local score storage and the likes. /// + /// + /// Only works under headless operation currently due to realm isolation difficulties. + /// If this is ever needed to change, consideration needs to be given to the fact that BeatmapManager is attached to the global realm. + /// protected virtual bool ImportBeatmapToDatabase => false; protected TestPlayer Player; @@ -77,6 +82,8 @@ namespace osu.Game.Tests.Visual if (ImportBeatmapToDatabase) { + Debug.Assert(DebugUtils.IsNUnitRunning, $@"Importing beatmaps in {nameof(PlayerTestScene)} requires headless environment."); + Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); var imported = beatmaps.Import(beatmap.BeatmapInfo.BeatmapSet); From 7ced84b7ef1ab6735ef42ab2c9a8f066008b8d8f Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 8 Jul 2022 03:23:58 -0500 Subject: [PATCH 081/139] Replace switch statement with ForModType In order to make `highlightedColour` dependent on the mod type color, the color is converted to an `osu.Framework.Graphics.Colour4` and calls `Lighten`. --- osu.Game/Rulesets/UI/ModIcon.cs | 38 +++++---------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index f32c298571..308122b71c 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -123,40 +123,12 @@ namespace osu.Game.Rulesets.UI modAcronym.FadeOut(); } - switch (value.Type) - { - default: - case ModType.DifficultyIncrease: - backgroundColour = colours.Red1; - highlightedColour = colours.Red0; - break; + Color4 typeColour = colours.ForModType(value.Type); + backgroundColour = typeColour; + highlightedColour = ((Colour4)typeColour).Lighten(.2f); - case ModType.DifficultyReduction: - backgroundColour = colours.Lime1; - highlightedColour = colours.Lime0; - break; - - case ModType.Automation: - backgroundColour = colours.Blue1; - highlightedColour = colours.Blue0; - break; - - case ModType.Conversion: - backgroundColour = colours.Purple1; - highlightedColour = colours.Purple0; - break; - - case ModType.Fun: - backgroundColour = colours.Pink1; - highlightedColour = colours.Pink0; - break; - - case ModType.System: - backgroundColour = colours.Gray7; - highlightedColour = colours.Gray8; - modIcon.Colour = colours.Yellow; - break; - } + if (value.Type == ModType.System) + modIcon.Colour = colours.Yellow; updateColour(); } From 13e16530a6d90c4fa341a2b3a3b7bec6e055eac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 18:44:50 +0900 Subject: [PATCH 082/139] Revert most changes to `PlayerTestScene` --- osu.Game/Tests/Visual/PlayerTestScene.cs | 32 ------------------------ 1 file changed, 32 deletions(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 5b4d4d5c2c..a9decbae57 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -4,16 +4,12 @@ #nullable disable using System; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Development; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; -using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -26,15 +22,6 @@ namespace osu.Game.Tests.Visual /// protected virtual bool HasCustomSteps => false; - /// - /// Import the beatmap to the database before starting gameplay. Handy for testing local score storage and the likes. - /// - /// - /// Only works under headless operation currently due to realm isolation difficulties. - /// If this is ever needed to change, consideration needs to be given to the fact that BeatmapManager is attached to the global realm. - /// - protected virtual bool ImportBeatmapToDatabase => false; - protected TestPlayer Player; protected OsuConfigManager LocalConfig; @@ -70,9 +57,6 @@ namespace osu.Game.Tests.Visual protected virtual bool Autoplay => false; - [Resolved] - private BeatmapManager beatmaps { get; set; } - protected void LoadPlayer() { var ruleset = CreatePlayerRuleset(); @@ -80,22 +64,6 @@ namespace osu.Game.Tests.Visual var beatmap = CreateBeatmap(ruleset.RulesetInfo); - if (ImportBeatmapToDatabase) - { - Debug.Assert(DebugUtils.IsNUnitRunning, $@"Importing beatmaps in {nameof(PlayerTestScene)} requires headless environment."); - - Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); - - var imported = beatmaps.Import(beatmap.BeatmapInfo.BeatmapSet); - - Debug.Assert(imported != null); - - beatmap.BeatmapInfo = null; - beatmap.Difficulty = null; - - beatmap.BeatmapInfo = imported.Value.Detach().Beatmaps.First(); - } - Beatmap.Value = CreateWorkingBeatmap(beatmap); SelectedMods.Value = Array.Empty(); From 3c8f06403cbe814f3f356e24f3bf055e2b275807 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 18:59:08 +0900 Subject: [PATCH 083/139] Make test work properly --- .../TestScenePlayerLocalScoreImport.cs | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 61f0483c86..a75d527a69 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -3,22 +3,51 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Extensions; +using osu.Framework.Platform; using osu.Framework.Screens; -using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Gameplay { - [HeadlessTest] // Importing rulesets doesn't work in interactive flows. public class TestScenePlayerLocalScoreImport : PlayerTestScene { - private Ruleset? customRuleset; + private BeatmapManager beatmaps = null!; + private RulesetStore rulesets = null!; - protected override bool ImportBeatmapToDatabase => true; + private BeatmapSetInfo? importedSet; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(Realm); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("import beatmap", () => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); + }); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => beatmaps.GetWorkingBeatmap(importedSet?.Beatmaps.First()).Beatmap; + + private Ruleset? customRuleset; protected override Ruleset CreatePlayerRuleset() => customRuleset ?? new OsuRuleset(); @@ -66,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); - AddUntilStep("score in database", () => Realm.Run(r => r.All().Count() == 1)); + AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); } } } From ac3cdf103a61bb3dced7b49c6c9f5dd0c357537c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 19:17:51 +0900 Subject: [PATCH 084/139] Fix test not failing against `master` --- .../TestScenePlayerLocalScoreImport.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index a75d527a69..96b7da9c9c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Tests.Resources; @@ -75,15 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestScoreStoredLocallyCustomRuleset() { - Ruleset createCustomRuleset() => new OsuRuleset - { - RulesetInfo = - { - Name = "custom", - ShortName = "custom", - OnlineID = -1 - } - }; + Ruleset createCustomRuleset() => new CustomRuleset(); AddStep("import custom ruleset", () => Realm.Write(r => r.Add(createCustomRuleset().RulesetInfo))); AddStep("set custom ruleset", () => customRuleset = createCustomRuleset()); @@ -97,5 +90,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); } + + private class CustomRuleset : OsuRuleset, ILegacyRuleset + { + public override string Description => "custom"; + public override string ShortName => "custom"; + + public new int LegacyID => -1; + + public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); + } } } From a606d545c193d2da6ac187b71fa0d461144d7468 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Fri, 8 Jul 2022 12:00:07 -0400 Subject: [PATCH 085/139] update new usage of CalculatePossibleMovementBounds --- .../Utils/OsuHitObjectGenerationUtils_Reposition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 436d71fda2..a9ae313a31 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Utils RotateSlider(slider, workingObject.RotationOriginal + MathF.PI - getSliderRotation(slider)); } - possibleMovementBounds = calculatePossibleMovementBounds(slider); + possibleMovementBounds = CalculatePossibleMovementBounds(slider); } var previousPosition = workingObject.PositionModified; From 8c7aabccb074070961b1d84a129277add1126eb2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 19:01:21 +0300 Subject: [PATCH 086/139] Fix custom legacy ID in test scene not overriding base value --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 96b7da9c9c..dc0a14ad9b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override string Description => "custom"; public override string ShortName => "custom"; - public new int LegacyID => -1; + int ILegacyRuleset.LegacyID => -1; public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); } From 667b1d795d6a7f9a58b9123ec397c60127a9de3a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 19:02:46 +0300 Subject: [PATCH 087/139] Ensure score has custom ruleset --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index dc0a14ad9b..3011515b62 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -83,6 +84,8 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(); + AddAssert("score has custom ruleset", () => Player.Score.ScoreInfo.Ruleset.Equals(customRuleset.AsNonNull().RulesetInfo)); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); From 66f314915dc3f2ed4a5d47857af5ee133b72d255 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 9 Jul 2022 06:01:22 +0900 Subject: [PATCH 088/139] Fix crash on mobile releases when attempting to read any file --- 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 93e554b43d..da1cdba73b 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, -1, leaveOpen); + streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen); } /// From 086388ec4ea2723fbd7e40719d9fa517503631c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sat, 9 Jul 2022 21:06:47 +0800 Subject: [PATCH 089/139] Remove nullable disable annotation in the benchmark project. --- osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs | 2 -- osu.Game.Benchmarks/BenchmarkMod.cs | 2 -- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 2 -- osu.Game.Benchmarks/BenchmarkRuleset.cs | 2 -- osu.Game.Benchmarks/BenchmarkTest.cs | 2 -- osu.Game.Benchmarks/Program.cs | 2 -- 6 files changed, 12 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs index 07ffda4030..1d207d04c7 100644 --- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs +++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.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.IO; using BenchmarkDotNet.Attributes; using osu.Framework.IO.Stores; diff --git a/osu.Game.Benchmarks/BenchmarkMod.cs b/osu.Game.Benchmarks/BenchmarkMod.cs index a1d92d9a67..c5375e9f09 100644 --- a/osu.Game.Benchmarks/BenchmarkMod.cs +++ b/osu.Game.Benchmarks/BenchmarkMod.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 BenchmarkDotNet.Attributes; using osu.Game.Rulesets.Osu.Mods; diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 5ffda6504e..2277f4e85c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.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 System.Threading; using BenchmarkDotNet.Attributes; diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index de8cb13773..2835ec9499 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.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 BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; using osu.Game.Online.API; diff --git a/osu.Game.Benchmarks/BenchmarkTest.cs b/osu.Game.Benchmarks/BenchmarkTest.cs index 140696e4a4..34f5edd084 100644 --- a/osu.Game.Benchmarks/BenchmarkTest.cs +++ b/osu.Game.Benchmarks/BenchmarkTest.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 BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using NUnit.Framework; diff --git a/osu.Game.Benchmarks/Program.cs b/osu.Game.Benchmarks/Program.cs index 603d8aa1b9..439ced53ab 100644 --- a/osu.Game.Benchmarks/Program.cs +++ b/osu.Game.Benchmarks/Program.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 BenchmarkDotNet.Configs; using BenchmarkDotNet.Running; From 8fcc33936c799b49ac4c3350ff84757a1c5d32a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sat, 9 Jul 2022 21:07:47 +0800 Subject: [PATCH 090/139] Mark the parameter as non-nullable. --- osu.Game.Benchmarks/BenchmarkMod.cs | 2 +- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 6 +++--- osu.Game.Benchmarks/BenchmarkRuleset.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkMod.cs b/osu.Game.Benchmarks/BenchmarkMod.cs index c5375e9f09..994300df36 100644 --- a/osu.Game.Benchmarks/BenchmarkMod.cs +++ b/osu.Game.Benchmarks/BenchmarkMod.cs @@ -9,7 +9,7 @@ namespace osu.Game.Benchmarks { public class BenchmarkMod : BenchmarkTest { - private OsuModDoubleTime mod; + private OsuModDoubleTime mod = null!; [Params(1, 10, 100)] public int Times { get; set; } diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 2277f4e85c..082eb5e51b 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -15,9 +15,9 @@ namespace osu.Game.Benchmarks { public class BenchmarkRealmReads : BenchmarkTest { - private TemporaryNativeStorage storage; - private RealmAccess realm; - private UpdateThread updateThread; + private TemporaryNativeStorage storage = null!; + private RealmAccess realm = null!; + private UpdateThread updateThread = null!; [Params(1, 100, 1000)] public int ReadsPerFetch { get; set; } diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index 2835ec9499..7d318e043b 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs @@ -11,9 +11,9 @@ namespace osu.Game.Benchmarks { public class BenchmarkRuleset : BenchmarkTest { - private OsuRuleset ruleset; - private APIMod apiModDoubleTime; - private APIMod apiModDifficultyAdjust; + private OsuRuleset ruleset = null!; + private APIMod apiModDoubleTime = null!; + private APIMod apiModDifficultyAdjust = null!; public override void SetUp() { From 378be99fe1cc9d2608ed4e0901e8bfd3164518e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sat, 9 Jul 2022 21:12:35 +0800 Subject: [PATCH 091/139] Remove un-need null check. --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 082eb5e51b..1df77320d2 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -133,9 +133,9 @@ namespace osu.Game.Benchmarks [GlobalCleanup] public void Cleanup() { - realm?.Dispose(); - storage?.Dispose(); - updateThread?.Exit(); + realm.Dispose(); + storage.Dispose(); + updateThread.Exit(); } } } From 5fcc4bf713937e1edbcae416532b3a29f43844dd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 9 Jul 2022 12:10:18 -0700 Subject: [PATCH 092/139] Add failing sample playback disabled check --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 8e325838ff..ff985de70e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -24,6 +24,7 @@ namespace osu.Game.Tests.Visual.Editing public void TestCantExitWithoutSaving() { AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10); + AddAssert("Sample playback disabled", () => Editor.SamplePlaybackDisabled.Value); AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor); } From 834bb1f187a268f25be9b031cfc2319b7c56e13c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 9 Jul 2022 12:14:39 -0700 Subject: [PATCH 093/139] Fix editor playing object samples while paused after cancelling exit --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 6ec9ff4e89..2b763415cd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -921,7 +921,7 @@ namespace osu.Game.Screens.Edit private void cancelExit() { - samplePlaybackDisabled.Value = false; + updateSampleDisabledState(); loader?.CancelPendingDifficultySwitch(); } From 52aef09cd6528bc8541f3d77305053dbce3da769 Mon Sep 17 00:00:00 2001 From: Ludio235 <94078268+Ludio235@users.noreply.github.com> Date: Sun, 10 Jul 2022 02:05:40 +0000 Subject: [PATCH 094/139] Update PlaylistsRoomSettingsOverlay.cs --- .../OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 1e565e298e..9c6a2a5e0b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -140,9 +140,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }, new Section("Duration") { - Child = DurationField = new DurationDropdown + Child = new Container { RelativeSizeAxes = Axes.X, + Height = 40, + Child = DurationField = new DurationDropdown + { + RelativeSizeAxes = Axes.X + } } }, new Section("Allowed attempts (across all playlist items)") From 6443338251e5c392ae71b3d4ee5410ddc6b36463 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Sun, 10 Jul 2022 01:22:22 -0400 Subject: [PATCH 095/139] use cursor position instead of destination for dampLength calculation the destination vector is clamped within playfield borders, we want dampLength to be based on distance from the cursor. --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 1cb5bf31a4..17bbba4ba7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -69,24 +69,24 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - easeTo(circle, destination); + easeTo(circle, destination, cursorPos); break; case DrawableSlider slider: if (!slider.HeadCircle.Result.HasResult) - easeTo(slider, destination); + easeTo(slider, destination, cursorPos); else - easeTo(slider, destination - slider.Ball.DrawPosition); + easeTo(slider, destination - slider.Ball.DrawPosition, cursorPos); break; } } } - private void easeTo(DrawableHitObject hitObject, Vector2 destination) + private void easeTo(DrawableHitObject hitObject, Vector2 destination, Vector2 cursorPos) { - double dampLength = Vector2.Distance(hitObject.Position, destination) / (0.04 * RepulsionStrength.Value + 0.04); + double dampLength = Vector2.Distance(hitObject.Position, cursorPos) / (0.04 * RepulsionStrength.Value + 0.04); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); From 8116a4b6f6a2779d74172c168afeee2f593d0bed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 10 Jul 2022 23:53:06 +0900 Subject: [PATCH 096/139] Fix multiplayer spectator crash due to track potentially not being loaded in time --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index bae25dc9f8..f5af110372 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -314,6 +314,9 @@ namespace osu.Game.Screens.OnlinePlay.Match public override void OnSuspending(ScreenTransitionEvent e) { + // Should be a noop in most cases, but let's ensure beyond doubt that the beatmap is in a correct state. + updateWorkingBeatmap(); + onLeaving(); base.OnSuspending(e); } From 8b6665cb5b211c06e24f9d3e9bf210480bc08760 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 02:51:54 +0900 Subject: [PATCH 097/139] Ensure initial beatmap processing is done inside the import transaction --- osu.Game/Database/RealmArchiveModelImporter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 6cea92f1d9..aa7fac07a8 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -338,11 +338,11 @@ namespace osu.Game.Database // import to store realm.Add(item); + PostImport(item, realm); + transaction.Commit(); } - PostImport(item, realm); - LogForModel(item, @"Import successfully completed!"); } catch (Exception e) @@ -479,7 +479,7 @@ namespace osu.Game.Database } /// - /// Perform any final actions after the import has been committed to the database. + /// Perform any final actions before the import has been committed to the database. /// /// The model prepared for import. /// The current realm context. From 0434c1091472e1582aa144e9f1ec77059acad4e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 02:57:44 +0900 Subject: [PATCH 098/139] Use global `WorkingBeatmap` in `PlayerArea` for the time being --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 5bae4f9ea5..302d04b531 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -53,9 +53,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [CanBeNull] public Score Score { get; private set; } - [Resolved] - private BeatmapManager beatmapManager { get; set; } - private readonly BindableDouble volumeAdjustment = new BindableDouble(); private readonly Container gameplayContent; private readonly LoadingLayer loadingLayer; @@ -84,6 +81,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate GameplayClock.Source = masterClock; } + [Resolved] + private IBindable beatmap { get; set; } + public void LoadScore([NotNull] Score score) { if (Score != null) @@ -91,7 +91,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Score = score; - gameplayContent.Child = new PlayerIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.BeatmapInfo), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) + gameplayContent.Child = new PlayerIsolationContainer(beatmap.Value, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, Child = stack = new OsuScreenStack From 958c0fb390ea04ac37f11e6547b8bb95ce9b31a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 15:01:16 +0900 Subject: [PATCH 099/139] Remove `Appveyor.TestLogger` --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 1 - .../osu.Game.Rulesets.Pippidon.Tests.csproj | 1 - .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 1 - .../osu.Game.Rulesets.Pippidon.Tests.csproj | 1 - .../osu.Game.Rulesets.Catch.Tests.csproj | 1 - .../osu.Game.Rulesets.Mania.Tests.csproj | 1 - osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 1 - .../osu.Game.Rulesets.Taiko.Tests.csproj | 1 - osu.Game.Tests/osu.Game.Tests.csproj | 1 - osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 1 - 10 files changed, 10 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index bc285dbe11..011a37cbdc 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -9,7 +9,6 @@ false - diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 718ada1905..c04f6132f3 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,7 +9,6 @@ false - diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 6b9c3f4d63..529054fd4f 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -9,7 +9,6 @@ false - diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 718ada1905..c04f6132f3 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,7 +9,6 @@ false - diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index b957ade952..3ac1491946 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index d3b4b378c0..d07df75864 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 2c0d3fd937..402cf3ad41 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index ce468d399b..51d4bbc630 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index a1eef4ce47..02fd6ccd16 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 6fd53d923b..5512b26863 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -4,7 +4,6 @@ osu.Game.Tournament.Tests.TournamentTestRunner - From 22a51fdc50f8444034049eca370c0aaaf19fc83f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 15:35:00 +0900 Subject: [PATCH 100/139] Add support for a drawings screen video background --- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 32da4d1b36..0df6386dae 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -97,6 +97,11 @@ namespace osu.Game.Tournament.Screens.Drawings FillMode = FillMode.Fill, Texture = textures.Get(@"Backgrounds/Drawings/background.png") }, + new TourneyVideo("drawings") + { + Loop = true, + RelativeSizeAxes = Axes.Both, + }, // Visualiser new VisualiserContainer { From 10d6027c8921937c2b8e100239174d87b0bee188 Mon Sep 17 00:00:00 2001 From: Andrew Hong <35881688+novialriptide@users.noreply.github.com> Date: Thu, 7 Jul 2022 23:16:06 -0400 Subject: [PATCH 101/139] Assign missing UserID to RealmUser --- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 28 +++++++++++++++++++++++++++- osu.Game/Scoring/ScoreManager.cs | 5 +++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a16252ac89..4b5c9c0815 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -272,7 +272,7 @@ namespace osu.Game 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, API, difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index f59ffc7c94..7494914625 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -8,11 +8,15 @@ using System.Threading; using Newtonsoft.Json; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.Models; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Rulesets; using osu.Game.Scoring.Legacy; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using Realms; namespace osu.Game.Scoring @@ -26,11 +30,14 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; - public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm) + private readonly IAPIProvider api; + + public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api) : base(storage, realm) { this.rulesets = rulesets; this.beatmaps = beatmaps; + this.api = api; } protected override ScoreInfo? CreateModel(ArchiveReader archive) @@ -68,5 +75,24 @@ namespace osu.Game.Scoring if (string.IsNullOrEmpty(model.StatisticsJson)) model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); } + + protected override void PostImport(ScoreInfo model, Realm realm) + { + base.PostImport(model, realm); + + var userRequest = new GetUserRequest(model.User.Username); + api.Perform(userRequest); + APIUser userReq = userRequest.Response; + + if (!(userReq is null)) { + Logger.Log($"Assignning UserID to RealmUser"); + var user = new RealmUser + { + OnlineID = userReq.Id, + Username = model.User.Username + }; + model.RealmUser = user; + } + } } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6ee1d11f83..9aed8904e6 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -21,6 +21,7 @@ using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; +using osu.Game.Online.API; namespace osu.Game.Scoring { @@ -31,7 +32,7 @@ namespace osu.Game.Scoring private readonly OsuConfigManager configManager; private readonly ScoreImporter scoreImporter; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, IAPIProvider api, BeatmapDifficultyCache difficultyCache = null, OsuConfigManager configManager = null) : base(storage, realm) { @@ -39,7 +40,7 @@ namespace osu.Game.Scoring this.difficultyCache = difficultyCache; this.configManager = configManager; - scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm) + scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api) { PostNotification = obj => PostNotification?.Invoke(obj) }; From 56896e8b415a7b9622a99561a98d48d001d52602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrew=20Hong=20=28=ED=99=8D=EC=A4=80=EC=9B=90=29?= <35881688+novialriptide@users.noreply.github.com> Date: Mon, 11 Jul 2022 02:20:39 -0400 Subject: [PATCH 102/139] Move PostImport() --- osu.Game/Database/RealmArchiveModelImporter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 6cea92f1d9..c143c51c6d 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -338,11 +338,11 @@ namespace osu.Game.Database // import to store realm.Add(item); + PostImport(item, realm); + transaction.Commit(); } - PostImport(item, realm); - LogForModel(item, @"Import successfully completed!"); } catch (Exception e) From 6220650ea3228930ebd27a46b9e4c8c3824abc73 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 11 Jul 2022 02:29:56 -0700 Subject: [PATCH 103/139] Fix dialog overlay not loading in time for headless test --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index ff985de70e..d7e9cc1bc0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -6,11 +6,13 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Overlays; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Select; @@ -23,6 +25,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestCantExitWithoutSaving() { + AddUntilStep("Wait for dialog overlay load", () => ((Drawable)Game.Dependencies.Get()).IsLoaded); AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10); AddAssert("Sample playback disabled", () => Editor.SamplePlaybackDisabled.Value); AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor); From 44d2e001ed4d80674beb281e32a69241294364fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 20:15:29 +0900 Subject: [PATCH 104/139] Update various dependencies --- osu.Desktop/osu.Desktop.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 14 +++++++------- osu.iOS.props | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index a4f9e2671b..c67017f175 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 402cf3ad41..4349d25cb3 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 02fd6ccd16..7615b3e8be 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 06b022ea44..1251ab800b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,12 +20,12 @@ - + - - - - + + + + @@ -38,8 +38,8 @@ - - + + diff --git a/osu.iOS.props b/osu.iOS.props index 9085205bbc..c38bb548bf 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -85,7 +85,7 @@ - + From 00c7101f540329333442390719113c3fedb25628 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 20:36:05 +0900 Subject: [PATCH 105/139] Remove `DrawingsScreen` world map completely --- .../Screens/Drawings/DrawingsScreen.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 0df6386dae..85afb9b2d0 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -12,8 +12,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Graphics; @@ -26,7 +24,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Drawings { - public class DrawingsScreen : TournamentScreen + public class DrawingsScreen : TournamentScreen, IProvideVideo { private const string results_filename = "drawings_results.txt"; @@ -45,7 +43,7 @@ namespace osu.Game.Tournament.Screens.Drawings public ITeamList TeamList; [BackgroundDependencyLoader] - private void load(TextureStore textures, Storage storage) + private void load(Storage storage) { RelativeSizeAxes = Axes.Both; @@ -91,12 +89,6 @@ namespace osu.Game.Tournament.Screens.Drawings RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Sprite - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - Texture = textures.Get(@"Backgrounds/Drawings/background.png") - }, new TourneyVideo("drawings") { Loop = true, From 73e924479fb6db7b5e57413ef08663c068805f3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 20:42:04 +0900 Subject: [PATCH 106/139] Find video by recursive check rather than marker interface Seems a lot more reliable, and allows falling back to the "main" video in cases which didn't support this previously. A next step may be to allow every screen to support a video based on its screen name, rather than specifying the local `TourneyVideo` every time. --- osu.Game.Tournament/Components/TourneyVideo.cs | 2 ++ .../Screens/Drawings/DrawingsScreen.cs | 2 +- .../Screens/Editors/TournamentEditorScreen.cs | 2 +- .../Screens/Gameplay/GameplayScreen.cs | 2 +- osu.Game.Tournament/Screens/IProvideVideo.cs | 14 -------------- osu.Game.Tournament/Screens/Ladder/LadderScreen.cs | 2 +- .../Screens/Schedule/ScheduleScreen.cs | 2 +- osu.Game.Tournament/Screens/Setup/SetupScreen.cs | 2 +- .../Screens/Showcase/ShowcaseScreen.cs | 2 +- .../Screens/TeamIntro/SeedingScreen.cs | 2 +- .../Screens/TeamIntro/TeamIntroScreen.cs | 2 +- .../Screens/TeamWin/TeamWinScreen.cs | 2 +- osu.Game.Tournament/TournamentSceneManager.cs | 3 ++- 13 files changed, 14 insertions(+), 25 deletions(-) delete mode 100644 osu.Game.Tournament/Screens/IProvideVideo.cs diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index c6bbb54f9a..2e79998e66 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -22,6 +22,8 @@ namespace osu.Game.Tournament.Components private Video video; private ManualClock manualClock; + public bool VideoAvailable => video != null; + public TourneyVideo(string filename, bool drawFallbackGradient = false) { this.filename = filename; diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 85afb9b2d0..5ac25f97b5 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -24,7 +24,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Drawings { - public class DrawingsScreen : TournamentScreen, IProvideVideo + public class DrawingsScreen : TournamentScreen { private const string results_filename = "drawings_results.txt"; diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs index 8af5bbe513..0fefe6f780 100644 --- a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs @@ -20,7 +20,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.Editors { - public abstract class TournamentEditorScreen : TournamentScreen, IProvideVideo + public abstract class TournamentEditorScreen : TournamentScreen where TDrawable : Drawable, IModelBacked where TModel : class, new() { diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 86b2c2a4e9..54ae4c0366 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -21,7 +21,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Gameplay { - public class GameplayScreen : BeatmapInfoScreen, IProvideVideo + public class GameplayScreen : BeatmapInfoScreen { private readonly BindableBool warmup = new BindableBool(); diff --git a/osu.Game.Tournament/Screens/IProvideVideo.cs b/osu.Game.Tournament/Screens/IProvideVideo.cs deleted file mode 100644 index aa67a5211f..0000000000 --- a/osu.Game.Tournament/Screens/IProvideVideo.cs +++ /dev/null @@ -1,14 +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 - -namespace osu.Game.Tournament.Screens -{ - /// - /// Marker interface for a screen which provides its own local video background. - /// - public interface IProvideVideo - { - } -} diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 23bfa84afc..7ad7e76a1f 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Ladder { - public class LadderScreen : TournamentScreen, IProvideVideo + public class LadderScreen : TournamentScreen { protected Container MatchesContainer; private Container paths; diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 7a11e26794..0827cbae69 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Schedule { - public class ScheduleScreen : TournamentScreen // IProvidesVideo + public class ScheduleScreen : TournamentScreen { private readonly Bindable currentMatch = new Bindable(); private Container mainContainer; diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index 42eff3565f..f472541d59 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.Setup { - public class SetupScreen : TournamentScreen, IProvideVideo + public class SetupScreen : TournamentScreen { private FillFlowContainer fillFlow; diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index 082aa99b0e..a7a175ceba 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -14,7 +14,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Showcase { - public class ShowcaseScreen : BeatmapInfoScreen // IProvideVideo + public class ShowcaseScreen : BeatmapInfoScreen { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 925c697346..719e0384d3 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamIntro { - public class SeedingScreen : TournamentMatchScreen, IProvideVideo + public class SeedingScreen : TournamentMatchScreen { private Container mainContainer; diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 98dfaa7487..08c9a7a897 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamIntro { - public class TeamIntroScreen : TournamentMatchScreen, IProvideVideo + public class TeamIntroScreen : TournamentMatchScreen { private Container mainContainer; diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 50207547cd..07557674e8 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamWin { - public class TeamWinScreen : TournamentMatchScreen, IProvideVideo + public class TeamWinScreen : TournamentMatchScreen { private Container mainContainer; diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 296b259d72..a12dbb4740 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -186,7 +187,7 @@ namespace osu.Game.Tournament var lastScreen = currentScreen; currentScreen = target; - if (currentScreen is IProvideVideo) + if (currentScreen.ChildrenOfType().FirstOrDefault()?.VideoAvailable == true) { video.FadeOut(200); From 09bfca4e4ad59da7574c8212c08f729b404aef10 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 11 Jul 2022 21:45:39 +0300 Subject: [PATCH 107/139] Fix build failing on tests --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 3011515b62..bfc06c0ee0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 1b9b59676b..ef0c7d7d4d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); return dependencies; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 8f890b2383..05b5c5c0cd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index cee917f6cf..e59914f69a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); return dependencies; From 4f009419b8d8402550a40664ae5dead774a6bd2c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 11 Jul 2022 21:46:42 +0300 Subject: [PATCH 108/139] Simplify population logic and match code style --- osu.Game/Scoring/ScoreImporter.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 7494914625..53dd511d57 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -8,7 +8,6 @@ using System.Threading; using Newtonsoft.Json; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Models; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; @@ -80,19 +79,12 @@ namespace osu.Game.Scoring { base.PostImport(model, realm); - var userRequest = new GetUserRequest(model.User.Username); - api.Perform(userRequest); - APIUser userReq = userRequest.Response; + var userRequest = new GetUserRequest(model.RealmUser.Username); - if (!(userReq is null)) { - Logger.Log($"Assignning UserID to RealmUser"); - var user = new RealmUser - { - OnlineID = userReq.Id, - Username = model.User.Username - }; - model.RealmUser = user; - } + api.Perform(userRequest); + + if (userRequest.Response is APIUser user) + model.RealmUser.OnlineID = user.Id; } } } From 54fe84350cfba996d83e81b150bd19df0e63d0e5 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Mon, 11 Jul 2022 17:23:32 -0400 Subject: [PATCH 109/139] reciprocate mod incompatibility --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 4c9418726c..a3f6448457 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) }; public bool PerformFail() => false; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 5a08df3803..84906f6eed 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel) }; private float theta; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 3fba2cefd2..8acd4fc422 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "They just won't stay still..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) }; private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles private const int wiggle_strength = 10; // Higher = stronger wiggles From 28278e2554246fa49764457bfdb09f33799f9289 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Mon, 11 Jul 2022 17:27:25 -0400 Subject: [PATCH 110/139] enable NRT again --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 17bbba4ba7..211987ee32 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -1,9 +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; +using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Utils; using osu.Game.Configuration; @@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) }; - private IFrameStableClock gameplayClock; + private IFrameStableClock? gameplayClock; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) @@ -86,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeTo(DrawableHitObject hitObject, Vector2 destination, Vector2 cursorPos) { + Debug.Assert(gameplayClock != null); + double dampLength = Vector2.Distance(hitObject.Position, cursorPos) / (0.04 * RepulsionStrength.Value + 0.04); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); From 76be9a829c3df4a39c1070203dab99e13216e4a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 12:59:55 +0900 Subject: [PATCH 111/139] Fix mutation after disposal in `TeamEditorScreen` --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 11db37c8b7..111893d18c 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -298,10 +298,10 @@ namespace osu.Game.Tournament.Screens.Editors }, true); } - private void updatePanel() + private void updatePanel() => Scheduler.AddOnce(() => { drawableContainer.Child = new UserGridPanel(user.ToAPIUser()) { Width = 300 }; - } + }); } } } From bae314a254b1c93aac01f9889bb0c34b8ce443b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 13:03:43 +0900 Subject: [PATCH 112/139] Add background on `SetupScreen` to hide video --- .../Screens/Setup/SetupScreen.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index f472541d59..2b2dce3664 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -9,6 +9,8 @@ using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Overlays; @@ -48,13 +50,21 @@ namespace osu.Game.Tournament.Screens.Setup { windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); - InternalChild = fillFlow = new FillFlowContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding(10), - Spacing = new Vector2(10), + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f), + }, + fillFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Spacing = new Vector2(10), + } }; api.LocalUser.BindValueChanged(_ => Schedule(reload)); @@ -74,7 +84,8 @@ namespace osu.Game.Tournament.Screens.Setup Action = () => sceneManager?.SetScreen(new StablePathSelectScreen()), Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found", Failing = fileBasedIpc?.IPCStorage == null, - Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation." + Description = + "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation." }, new ActionableInfo { From 90fecbc9c7435ff5c2f3077b8fdebb96ff3d6c52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 14:32:39 +0900 Subject: [PATCH 113/139] Add test showing all mod icons for reference --- .../Visual/UserInterface/TestSceneModIcon.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs index 0a0415789a..ce9aa682d1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs @@ -1,10 +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.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; @@ -13,10 +13,24 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneModIcon : OsuTestScene { + [Test] + public void TestShowAllMods() + { + AddStep("create mod icons", () => + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Full, + ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods().Select(m => new ModIcon(m)), + }; + }); + } + [Test] public void TestChangeModType() { - ModIcon icon = null; + ModIcon icon = null!; AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime())); AddStep("change mod", () => icon.Mod = new OsuModEasy()); @@ -25,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestInterfaceModType() { - ModIcon icon = null; + ModIcon icon = null!; var ruleset = new OsuRuleset(); From 8dbe24fd7c05d8f30cf500b8d77dfa17094c2320 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 14:36:50 +0900 Subject: [PATCH 114/139] Simplify colour assigning logic and remove system mod colour for now --- osu.Game/Rulesets/UI/ModIcon.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 308122b71c..b1f355a789 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osuTK; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Localisation; namespace osu.Game.Rulesets.UI @@ -53,7 +54,6 @@ namespace osu.Game.Rulesets.UI private OsuColour colours { get; set; } private Color4 backgroundColour; - private Color4 highlightedColour; /// /// Construct a new instance. @@ -123,19 +123,13 @@ namespace osu.Game.Rulesets.UI modAcronym.FadeOut(); } - Color4 typeColour = colours.ForModType(value.Type); - backgroundColour = typeColour; - highlightedColour = ((Colour4)typeColour).Lighten(.2f); - - if (value.Type == ModType.System) - modIcon.Colour = colours.Yellow; - + backgroundColour = colours.ForModType(value.Type); updateColour(); } private void updateColour() { - background.Colour = Selected.Value ? highlightedColour : backgroundColour; + background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour; } } } From b52ea161336ce148e1d511dbb9111289ad9f2972 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 15:10:59 +0900 Subject: [PATCH 115/139] Show basic error message when score submission fails --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 1cc0d1853d..c916791eaa 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Play request.Failure += e => { - Logger.Error(e, "Failed to submit score"); + Logger.Error(e, $"Failed to submit score ({e.Message})"); scoreSubmissionSource.SetResult(false); }; From fa626a82b3448b8da0130cadff09e240e236ba39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 15:18:52 +0900 Subject: [PATCH 116/139] Add missed incompatilibity rules --- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index d562c37541..490b5b7a9d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModAutoplay : ModAutoplay { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray(); public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 2cf8c278ca..5c1de83972 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAlternate) }).ToArray(); /// /// How early before a hitobject's start time to trigger a hit. From cafe30fc4dc0d21dc885dd224568b88f6b4f545a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 14:54:45 +0900 Subject: [PATCH 117/139] Fix potential crash during shutdown sequence if intro playback was aborted Fixes one of the audio related `ObjectDisposedException`s (https://sentry.ppy.sh/organizations/ppy/issues/92/events/12f282f048cb4a4fae85810e8a70b68d/?project=2&query=is%3Aunresolved&sort=freq&statsPeriod=7d). Ran into this while testing locally. See `IntroScreen.ensureEventuallyArrivingAtMenu` for the related cause of this happening (forced continuing to next screen if the intro doesn't load in time). --- osu.Game/Screens/Menu/IntroTriangles.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 3cdf51a87c..c228faae58 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -72,9 +72,16 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both, Clock = decoupledClock, LoadMenu = LoadMenu - }, t => + }, _ => { - AddInternal(t); + AddInternal(intro); + + // There is a chance that the intro timed out before being displayed, and this scheduled callback could + // happen during the outro rather than intro. In such a scenario the game may already be in a disposing state + // which will trigger errors during attempted audio playback. + if (DidLoadMenu) + return; + if (!UsingThemedIntro) welcome?.Play(); From 10a14f39ed3ead07664dcf24edc0b70dc469c717 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 15:42:20 +0900 Subject: [PATCH 118/139] Show an error message on startup when attempting to run on an unsupported version of windows A lot of sentry error reports are coming from realm / EF failures due to the host operating system being too old. Let's give the user some proper feedback rather than a silent crash and error report hitting our logging. --- osu.Desktop/Program.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 712f300671..da2a580086 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -14,6 +14,7 @@ using osu.Framework.Platform; using osu.Game; using osu.Game.IPC; using osu.Game.Tournament; +using SDL2; using Squirrel; namespace osu.Desktop @@ -29,7 +30,19 @@ namespace osu.Desktop { // run Squirrel first, as the app may exit after these run if (OperatingSystem.IsWindows()) + { + var windowsVersion = Environment.OSVersion.Version; + + if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 1)) + { + SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, + "Your operating system is too old to run osu!", + "This version of osu! requires at least Windows 8 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero); + return; + } + setupSquirrel(); + } // Back up the cwd before DesktopGameHost changes it string cwd = Environment.CurrentDirectory; From a36f7867250558ceebcb947ee489564401601b81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 16:16:46 +0900 Subject: [PATCH 119/139] Change minimum version to Windows 8.1 instead of Windows 8 --- osu.Desktop/Program.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index da2a580086..66b361cb73 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -33,7 +33,9 @@ namespace osu.Desktop { var windowsVersion = Environment.OSVersion.Version; - if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 1)) + // While .NET 6 still supports Windows 7 and above, we are limited by realm currently, as they choose to only support 8.1 and higher. + // See https://www.mongodb.com/docs/realm/sdk/dotnet/#supported-platforms + if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)) { SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, "Your operating system is too old to run osu!", From cad18ebc5844277bad61c50cac8a40c429c5da80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 16:46:19 +0900 Subject: [PATCH 120/139] Reword comment to better explain what we are guarding against --- osu.Game/Screens/Menu/IntroTriangles.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index c228faae58..6ad0350e43 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -77,8 +77,9 @@ namespace osu.Game.Screens.Menu AddInternal(intro); // There is a chance that the intro timed out before being displayed, and this scheduled callback could - // happen during the outro rather than intro. In such a scenario the game may already be in a disposing state - // which will trigger errors during attempted audio playback. + // happen during the outro rather than intro. + // In such a scenario, we don't want to play the intro sample, nor attempt to start the intro track + // (that may have already been since disposed by MusicController). if (DidLoadMenu) return; From b96734e31a9184b2a636b5435d122d7d5b3d32df Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 12 Jul 2022 08:43:48 -0400 Subject: [PATCH 121/139] fix mod incompatibility between repel and relax --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 5c1de83972..2cf8c278ca 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray(); /// /// How early before a hitobject's start time to trigger a hit. From c04658584285fb302d8df96e56279ba1fe59a806 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 12 Jul 2022 18:29:17 +0300 Subject: [PATCH 122/139] Fix unsupported OS message stating Windows 8 to be supported --- osu.Desktop/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 66b361cb73..cebbcb40b7 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -39,7 +39,7 @@ namespace osu.Desktop { SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, "Your operating system is too old to run osu!", - "This version of osu! requires at least Windows 8 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero); + "This version of osu! requires at least Windows 8.1 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero); return; } From 27ef7fc78eda72a56fda70003f5f60e7d51430b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:22:50 +0900 Subject: [PATCH 123/139] Add log output for custom storage usage Sometimes I am not sure where my osu! is reading files from. This should help somewhat. ```csharp /Users/dean/Projects/osu/osu.Desktop/bin/Debug/net6.0/osu! [runtime] 2022-07-13 07:22:03 [verbose]: Starting legacy IPC provider... [runtime] 2022-07-13 07:22:03 [verbose]: Attempting to use custom storage location /Users/dean/Games/osu-lazer-2 [runtime] 2022-07-13 07:22:03 [verbose]: Storage successfully changed to /Users/dean/Games/osu-lazer-2. [runtime] 2022-07-13 07:22:05 [verbose]: GL Initialized ``` --- osu.Game/IO/OsuStorage.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 89bdd09f0d..368ac56850 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -94,6 +94,8 @@ namespace osu.Game.IO error = OsuStorageError.None; Storage lastStorage = UnderlyingStorage; + Logger.Log($"Attempting to use custom storage location {CustomStoragePath}"); + try { Storage userStorage = host.GetStorage(CustomStoragePath); @@ -102,6 +104,7 @@ namespace osu.Game.IO error = OsuStorageError.AccessibleButEmpty; ChangeTargetStorage(userStorage); + Logger.Log($"Storage successfully changed to {CustomStoragePath}."); } catch { @@ -109,6 +112,9 @@ namespace osu.Game.IO ChangeTargetStorage(lastStorage); } + if (error != OsuStorageError.None) + Logger.Log($"Custom storage location could not be used ({error})."); + return error == OsuStorageError.None; } From 8820ea4006b1bd2119071a51fa5d1c786ff26014 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:36:43 +0900 Subject: [PATCH 124/139] Add last played date to `BeatmapInfo` --- osu.Game/Beatmaps/BeatmapInfo.cs | 5 +++++ osu.Game/Database/RealmAccess.cs | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 346bf86818..3b851e05c0 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -110,6 +110,11 @@ namespace osu.Game.Beatmaps public bool SamplesMatchPlaybackRate { get; set; } = true; + /// + /// The time at which this beatmap was last played by the local user. + /// + public DateTimeOffset? LastPlayed { get; set; } + /// /// The ratio of distance travelled per time unit. /// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see ). diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 8cf57b802b..02b5a51f1f 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -58,8 +58,9 @@ namespace osu.Game.Database /// 12 2021-11-24 Add Status to RealmBeatmapSet. /// 13 2022-01-13 Final migration of beatmaps and scores to realm (multiple new storage fields). /// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo. + /// 15 2022-07-13 Added LastPlayed to BeatmapInfo. /// - private const int schema_version = 14; + private const int schema_version = 15; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. From 4b96d74b0cd7d800b0e6cdbe9caffc00a8e3ec6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:43:39 +0900 Subject: [PATCH 125/139] Add test coverage of `LastPlayed` updating --- .../Gameplay/TestScenePlayerLocalScoreImport.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index bfc06c0ee0..5ec9e88728 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.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.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -59,6 +60,20 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool AllowFail => false; + [Test] + public void TestLastPlayedUpdated() + { + DateTimeOffset? getLastPlayed() => Realm.Run(r => r.Find(Beatmap.Value.BeatmapInfo.ID)?.LastPlayed); + + AddStep("set no custom ruleset", () => customRuleset = null); + AddAssert("last played is null", () => getLastPlayed() == null); + + CreateTest(); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + AddUntilStep("wait for last played to update", () => getLastPlayed() != null); + } + [Test] public void TestScoreStoredLocally() { From ab3ec80159f6b20e9eb0c470287fb1562267f971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:36:55 +0900 Subject: [PATCH 126/139] Update `LastPlayed` on gameplay starting in a `SubmittingPlayer` --- osu.Game/Screens/Play/SubmittingPlayer.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index c916791eaa..f3bc0ea798 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -11,6 +11,8 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Scoring; @@ -117,6 +119,23 @@ namespace osu.Game.Screens.Play await submitScore(score).ConfigureAwait(false); } + [Resolved] + private RealmAccess realm { get; set; } + + protected override void StartGameplay() + { + base.StartGameplay(); + + // User expectation is that last played should be updated when entering the gameplay loop + // from multiplayer / playlists / solo, even when using autoplay mod. + realm.WriteAsync(r => + { + var realmBeatmap = r.Find(Beatmap.Value.BeatmapInfo.ID); + if (realmBeatmap != null) + realmBeatmap.LastPlayed = DateTimeOffset.Now; + }); + } + public override bool OnExiting(ScreenExitEvent e) { bool exiting = base.OnExiting(e); From fc274629f8e574ac70dcc0895e6762b67a4a418a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:37:05 +0900 Subject: [PATCH 127/139] Add "last played" sort mode to song select Note that this will consider the most recent play of any beatmap in beatmap set groups for now, similar to other sort methods. --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 3 +++ osu.Game/Screens/Select/Filter/SortMode.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 36ec536780..94d911692c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -81,6 +81,9 @@ namespace osu.Game.Screens.Select.Carousel case SortMode.DateAdded: return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); + case SortMode.LastPlayed: + return -compareUsingAggregateMax(otherSet, b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds()); + case SortMode.BPM: return compareUsingAggregateMax(otherSet, b => b.BPM); diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs index 48f774393e..4227114618 100644 --- a/osu.Game/Screens/Select/Filter/SortMode.cs +++ b/osu.Game/Screens/Select/Filter/SortMode.cs @@ -23,6 +23,9 @@ namespace osu.Game.Screens.Select.Filter [Description("Date Added")] DateAdded, + [Description("Last Played")] + LastPlayed, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingDifficulty))] Difficulty, From b9ad90ce54ecf92709de51df29642d38a0bc6783 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 17:57:45 +0900 Subject: [PATCH 128/139] Switch `TeamWinScreen` scheduling to `AddOnce` --- osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 07557674e8..ac54ff58f5 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tournament.Screens.TeamWin private bool firstDisplay = true; - private void update() => Schedule(() => + private void update() => Scheduler.AddOnce(() => { var match = CurrentMatch.Value; From 4dff999ce665b20cd87b1fdb7064863fe8da6286 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:09:54 +0900 Subject: [PATCH 129/139] Fix potential null referenced in `SeedingScreen` Also ensure that any update operations only occur when the seeding screen is displayed. They were running in the background until now. --- osu.Game.Tournament/Models/SeedingBeatmap.cs | 4 +--- .../Screens/TeamIntro/SeedingScreen.cs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/Models/SeedingBeatmap.cs b/osu.Game.Tournament/Models/SeedingBeatmap.cs index 03beb7ca9a..fb0e20556c 100644 --- a/osu.Game.Tournament/Models/SeedingBeatmap.cs +++ b/osu.Game.Tournament/Models/SeedingBeatmap.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 Newtonsoft.Json; using osu.Framework.Bindables; @@ -13,7 +11,7 @@ namespace osu.Game.Tournament.Models public int ID; [JsonProperty("BeatmapInfo")] - public TournamentBeatmap Beatmap; + public TournamentBeatmap? Beatmap; public long Score; diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 719e0384d3..4970d520af 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro currentTeam.BindValueChanged(teamChanged, true); } - private void teamChanged(ValueChangedEvent team) + private void teamChanged(ValueChangedEvent team) => Scheduler.AddOnce(() => { if (team.NewValue == null) { @@ -78,7 +78,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro } showTeam(team.NewValue); - } + }); protected override void CurrentMatchChanged(ValueChangedEvent match) { @@ -120,8 +120,14 @@ namespace osu.Game.Tournament.Screens.TeamIntro foreach (var seeding in team.SeedingResults) { fill.Add(new ModRow(seeding.Mod.Value, seeding.Seed.Value)); + foreach (var beatmap in seeding.Beatmaps) + { + if (beatmap.Beatmap == null) + continue; + fill.Add(new BeatmapScoreRow(beatmap)); + } } } @@ -157,7 +163,8 @@ namespace osu.Game.Tournament.Screens.TeamIntro Children = new Drawable[] { new TournamentSpriteText { Text = beatmap.Score.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Width = 80 }, - new TournamentSpriteText { Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, + new TournamentSpriteText + { Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, } }, }; From 1516756d8bfa3de7a26966be4ad083b5b800768c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:10:27 +0900 Subject: [PATCH 130/139] Fix team name not updating on `TeamDisplay` immediately --- .../Components/TournamentSpriteTextWithBackground.cs | 3 ++- .../Screens/Gameplay/Components/TeamDisplay.cs | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs index 0fc3646585..b088670caa 100644 --- a/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs +++ b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs @@ -12,7 +12,8 @@ namespace osu.Game.Tournament.Components { public class TournamentSpriteTextWithBackground : CompositeDrawable { - protected readonly TournamentSpriteText Text; + public readonly TournamentSpriteText Text; + protected readonly Box Background; public TournamentSpriteTextWithBackground(string text = "") diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index bb187c9e67..1eceddd871 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -16,6 +16,10 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { private readonly TeamScore score; + private readonly TournamentSpriteTextWithBackground teamText; + + private readonly Bindable teamName = new Bindable("???"); + private bool showScore; public bool ShowScore @@ -93,7 +97,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components } } }, - new TournamentSpriteTextWithBackground(team?.FullName.Value ?? "???") + teamText = new TournamentSpriteTextWithBackground { Scale = new Vector2(0.5f), Origin = anchor, @@ -113,6 +117,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components updateDisplay(); FinishTransforms(true); + + if (Team != null) + teamName.BindTo(Team.FullName); + + teamName.BindValueChanged(name => teamText.Text.Text = name.NewValue, true); } private void updateDisplay() From 5c6fa2341f8fe3bcfdc02df08652538fd82fd3d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:15:59 +0900 Subject: [PATCH 131/139] Fix `TeamScoreDisplay` not tracking team changes properly --- .../Screens/Gameplay/Components/TeamScoreDisplay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs index ed11f097ed..5ee57e9271 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs @@ -42,6 +42,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components currentMatch.BindTo(ladder.CurrentMatch); currentMatch.BindValueChanged(matchChanged); + currentTeam.BindValueChanged(teamChanged); + updateMatch(); } @@ -67,7 +69,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components // team may change to same team, which means score is not in a good state. // thus we handle this manually. - teamChanged(currentTeam.Value); + currentTeam.TriggerChange(); } protected override bool OnMouseDown(MouseDownEvent e) @@ -88,11 +90,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components return base.OnMouseDown(e); } - private void teamChanged(TournamentTeam team) + private void teamChanged(ValueChangedEvent team) { InternalChildren = new Drawable[] { - teamDisplay = new TeamDisplay(team, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0), + teamDisplay = new TeamDisplay(team.NewValue, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0), }; } } From 214351a87e3022eed3e929cd866ef4e1511ac553 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:32:47 +0900 Subject: [PATCH 132/139] Ensure any changes are committed before changing `LadderEditorSettings`'s target match --- .../Screens/Ladder/Components/LadderEditorSettings.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index f0eda5672a..1fdf616e34 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -53,6 +53,9 @@ namespace osu.Game.Tournament.Screens.Ladder.Components editorInfo.Selected.ValueChanged += selection => { + // ensure any ongoing edits are committed out to the *current* selection before changing to a new one. + GetContainingInputManager().TriggerFocusContention(null); + roundDropdown.Current = selection.NewValue?.Round; losersCheckbox.Current = selection.NewValue?.Losers; dateTimeBox.Current = selection.NewValue?.Date; From 467f83b603dcdba943c3d2f9e8341eaa55127186 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:48:12 +0900 Subject: [PATCH 133/139] Add non-null assertion missing in `BeatmapScoreRow` --- osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 4970d520af..9262cab098 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -135,6 +136,8 @@ namespace osu.Game.Tournament.Screens.TeamIntro { public BeatmapScoreRow(SeedingBeatmap beatmap) { + Debug.Assert(beatmap.Beatmap != null); + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From 952d97c66e98de0f5e90cc285b329b2abf26463b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:02:21 +0900 Subject: [PATCH 134/139] Update comment regarding `LoadTrack` safety --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2b763415cd..48576b81e2 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Edit loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); // required so we can get the track length in EditorClock. - // this is safe as nothing has yet got a reference to this new beatmap. + // this is ONLY safe because the track being provided is a `TrackVirtual` which we don't really care about disposing. loadableBeatmap.LoadTrack(); // this is a bit haphazard, but guards against setting the lease Beatmap bindable if From 6950223a7dd12a3ce84daf2ef48d9ee1e9a07784 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:18:38 +0900 Subject: [PATCH 135/139] Fix drawable mutation from disposal thread --- osu.Game/Screens/Select/FooterButtonMods.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 2732b5baa8..c938f58984 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Select Current.BindValueChanged(_ => updateMultiplierText(), true); } - private void updateMultiplierText() + private void updateMultiplierText() => Schedule(() => { double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1; @@ -85,6 +85,6 @@ namespace osu.Game.Screens.Select modDisplay.FadeIn(); else modDisplay.FadeOut(); - } + }); } } From c6b6f41b717be33f1595b9effe4a6a4f8845d038 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:40:57 +0900 Subject: [PATCH 136/139] Add test coverage of `AudioEquals` --- .../NonVisual/BeatmapSetInfoEqualityTest.cs | 44 +++++++++++++++++++ osu.Game.Tests/Resources/TestResources.cs | 1 + 2 files changed, 45 insertions(+) diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs index de23b012c1..c887105da6 100644 --- a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs @@ -4,9 +4,12 @@ #nullable disable using System; +using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Extensions; +using osu.Game.Models; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.NonVisual { @@ -23,6 +26,47 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(ourInfo.MatchesOnlineID(otherInfo)); } + [Test] + public void TestAudioEqualityNoFile() + { + var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1); + var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1); + + Assert.AreNotEqual(beatmapSetA, beatmapSetB); + Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single())); + } + + [Test] + public void TestAudioEqualitySameHash() + { + var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1); + var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1); + + addAudioFile(beatmapSetA, "abc"); + addAudioFile(beatmapSetB, "abc"); + + Assert.AreNotEqual(beatmapSetA, beatmapSetB); + Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single())); + } + + [Test] + public void TestAudioEqualityDifferentHash() + { + var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1); + var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1); + + addAudioFile(beatmapSetA); + addAudioFile(beatmapSetB); + + Assert.AreNotEqual(beatmapSetA, beatmapSetB); + Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single())); + } + + private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null) + { + beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, "audio.mp3")); + } + [Test] public void TestDatabasedWithDatabased() { diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 91d4eb70e8..41404b2636 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -134,6 +134,7 @@ namespace osu.Game.Tests.Resources DifficultyName = $"{version} {beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", StarRating = diff, Length = length, + BeatmapSet = beatmapSet, BPM = bpm, Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), Ruleset = rulesetInfo, From 1cfdea911b7aec4ce7a354480b1818d61a5cbd33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:13:18 +0900 Subject: [PATCH 137/139] Fix audio and background file equality incorrectly comparing `BeatmapSet.Hash` --- osu.Game/Beatmaps/BeatmapInfo.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 346bf86818..531dc7deca 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; @@ -151,14 +152,23 @@ namespace osu.Game.Beatmaps public bool AudioEquals(BeatmapInfo? other) => other != null && BeatmapSet != null && other.BeatmapSet != null - && BeatmapSet.Hash == other.BeatmapSet.Hash - && Metadata.AudioFile == other.Metadata.AudioFile; + && compareFiles(this, other, m => m.AudioFile); public bool BackgroundEquals(BeatmapInfo? other) => other != null && BeatmapSet != null && other.BeatmapSet != null - && BeatmapSet.Hash == other.BeatmapSet.Hash - && Metadata.BackgroundFile == other.Metadata.BackgroundFile; + && compareFiles(this, other, m => m.BackgroundFile); + + private static bool compareFiles(BeatmapInfo x, BeatmapInfo y, Func getFilename) + { + Debug.Assert(x.BeatmapSet != null); + Debug.Assert(y.BeatmapSet != null); + + string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.BeatmapSet.Metadata))?.File.Hash; + string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.BeatmapSet.Metadata))?.File.Hash; + + return fileHashX == fileHashY; + } IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata; IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet; From 2e86e7ccee2e802c01e17182f15145ec436988e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:31:53 +0900 Subject: [PATCH 138/139] Add extra steps to `TestExitWithoutSave` to guarantee track type --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index f565ca3ef4..6ad6f0b299 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Screens; @@ -103,6 +104,8 @@ namespace osu.Game.Tests.Visual.Editing */ public void TestAddAudioTrack() { + AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual); + AddAssert("switch track to real track", () => { var setup = Editor.ChildrenOfType().First(); @@ -131,6 +134,7 @@ namespace osu.Game.Tests.Visual.Editing } }); + AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual); AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000); } From 937692604edbc919e6a55d6499ca6a0e72bc69c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 22:37:20 +0900 Subject: [PATCH 139/139] Remove mention of autoplay mod for now --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index f3bc0ea798..ad63925b93 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Play base.StartGameplay(); // User expectation is that last played should be updated when entering the gameplay loop - // from multiplayer / playlists / solo, even when using autoplay mod. + // from multiplayer / playlists / solo. realm.WriteAsync(r => { var realmBeatmap = r.Find(Beatmap.Value.BeatmapInfo.ID);